API examples¶
The QGIS Publish Service does not implement login. Authenticate with Central first, then supply its session cookie to the same platform host. The examples assume:
ORIGIN=https://app.example.com
BASE=/qgis-publish-service
COOKIE=acugis_session=<session-value-issued-by-Central>
PROJECT=city-map
Do not copy a cookie into source control. Central may also support bearer credentials, but cookie authentication is shown because the QGIS service resolves Central sessions directly.
curl¶
List only projects the session can view:
Publish a QGIS package, capture its ID, then add metadata:
UPLOAD_JSON=$(
curl -fsS \
-H 'Accept: application/json' \
-H "Cookie: $COOKIE" \
-F 'file=@project.qgz;type=application/octet-stream' \
"$ORIGIN$BASE/projects/upload"
)
# Set PROJECT from the "id" property using your JSON tool.
curl -fsS -X PUT \
-H 'Content-Type: application/json' \
-H "Cookie: $COOKIE" \
--data '{"title":"City map","description":"Published from the API","tags":["city","demo"]}' \
"$ORIGIN$BASE/api/projects/$PROJECT/metadata"
Inspect layers and WMS capabilities:
curl -fsS -H "Cookie: $COOKIE" \
"$ORIGIN$BASE/api/projects/$PROJECT/layers"
curl -fsS -H "Cookie: $COOKIE" \
"$ORIGIN$BASE/qgis/$PROJECT?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities"
Update and restore:
curl -fsS -H "Cookie: $COOKIE" \
-F 'file=@project-v2.zip;type=application/zip' \
"$ORIGIN$BASE/projects/$PROJECT/update"
curl -fsS -H "Cookie: $COOKIE" \
"$ORIGIN$BASE/projects/$PROJECT/versions"
curl -fsS -X POST -H "Cookie: $COOKIE" \
"$ORIGIN$BASE/projects/$PROJECT/versions/1/restore"
Create and update a PostGIS feature (field names must be writable in the published QGIS form):
curl -fsS -X POST \
-H 'Content-Type: application/json' -H "Cookie: $COOKIE" \
--data '{"layer":"assets","attributes":{"name":"Pump 12","status":"active"},"geometry":{"type":"Point","coordinates":[-73.99,40.73]}}' \
"$ORIGIN$BASE/api/projects/$PROJECT/features/create"
curl -fsS -X POST \
-H 'Content-Type: application/json' -H "Cookie: $COOKIE" \
--data '{"layer":"assets","feature_id":12,"attributes":{"status":"inactive"}}' \
"$ORIGIN$BASE/api/projects/$PROJECT/features/update"
Seed cached tiles and poll the returned job_id:
curl -fsS -X POST \
-H 'Content-Type: application/json' -H "Cookie: $COOKIE" \
--data '{"min_zoom":0,"max_zoom":8,"bbox":[-8238310,4968191,-8230000,4977000]}' \
"$ORIGIN$BASE/projects/$PROJECT/seed"
# JOB is the job_id from the previous response.
curl -fsS "$ORIGIN$BASE/cache/api/seed/status?id=$JOB"
JavaScript¶
Browser code automatically sends the Central cookie only when credentials: "include" is set and same-origin/cookie policy permits it. It must not set a Cookie header directly.
const origin = "https://app.example.com";
const base = "/qgis-publish-service";
async function api(path, options = {}) {
const response = await fetch(origin + base + path, {
credentials: "include",
headers: { Accept: "application/json", ...(options.headers || {}) },
...options,
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
if (response.status === 204) return null;
return response.json();
}
const projects = await api("/projects");
const project = await api(`/projects/${encodeURIComponent(projects[0].id)}`);
const layers = await api(`/api/projects/${encodeURIComponent(project.id)}/layers`);
console.log(project, layers);
Publish and set metadata:
const form = new FormData();
form.append("file", fileInput.files[0]);
const created = await api("/projects/upload", { method: "POST", body: form });
const metadata = await api(`/api/projects/${encodeURIComponent(created.id)}/metadata`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "City map",
description: "Published from JavaScript",
tags: ["city"],
}),
});
console.log(metadata);
Create a browser map. map_config is validated by the server and its detailed schema is dynamic to the Map Builder implementation:
const browserMap = await api("/api/browser-maps", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Operations overview",
description: "",
visibility: "restricted",
map_config: { version: 1, layers: [] },
}),
});
Python¶
Supply a session cookie value obtained from Central:
from pathlib import Path
import requests
ORIGIN = "https://app.example.com"
BASE = "/qgis-publish-service"
PROJECT = "city-map"
s = requests.Session()
s.cookies.set("acugis_session", "<session-value-issued-by-Central>")
s.headers["Accept"] = "application/json"
r = s.get(f"{ORIGIN}{BASE}/projects", timeout=60)
r.raise_for_status()
print(r.json())
with Path("project.qgz").open("rb") as handle:
r = s.post(
f"{ORIGIN}{BASE}/projects/upload",
files={"file": ("project.qgz", handle, "application/octet-stream")},
timeout=(30, 600),
)
r.raise_for_status()
project_id = r.json()["id"]
r = s.put(
f"{ORIGIN}{BASE}/api/projects/{project_id}/metadata",
json={"title": "City map", "description": "Published from Python", "tags": []},
timeout=60,
)
r.raise_for_status()
print(r.json())
Run an analysis:
r = s.get(
f"{ORIGIN}{BASE}/api/projects/{PROJECT}/outliers-data",
params={
"layer": "assets",
"field": "pressure",
"label_field": "name",
"threshold": 2,
"scope": "layer",
},
timeout=60,
)
r.raise_for_status()
for row in r.json()["rows"]:
if row["classification"] != "Normal":
print(row)
Go¶
The cookie jar can be populated from a Central login performed elsewhere. This example uses a supplied cookie and publishes a file.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
)
func main() {
origin := "https://app.example.com"
base := "/qgis-publish-service"
u, _ := url.Parse(origin)
jar, _ := cookiejar.New(nil)
jar.SetCookies(u, []*http.Cookie{{Name: "acugis_session", Value: "<session-value-issued-by-Central>"}})
client := &http.Client{Jar: jar}
resp, err := client.Get(origin + base + "/projects")
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { panic(resp.Status) }
io.Copy(os.Stdout, resp.Body)
var body bytes.Buffer
mw := multipart.NewWriter(&body)
part, _ := mw.CreateFormFile("file", "project.qgz")
f, _ := os.Open("project.qgz")
io.Copy(part, f)
f.Close()
mw.Close()
req, _ := http.NewRequest(http.MethodPost, origin+base+"/projects/upload", &body)
req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "application/json")
resp, err = client.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(resp.Status) }
var created map[string]any
json.NewDecoder(resp.Body).Decode(&created)
fmt.Println(created["id"])
}
Update render mode:
payload := bytes.NewBufferString(`{"render_mode":"cache"}`)
req, _ := http.NewRequest(http.MethodPost, origin+base+"/projects/city-map/render-mode", payload)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
PHP¶
Use cURL with a supplied Central cookie. The first request lists projects; the second publishes a package and stores metadata.
<?php
$origin = 'https://app.example.com';
$base = '/qgis-publish-service';
$cookie = 'acugis_session=<session-value-issued-by-Central>';
function request($method, $url, $cookie, $body = null, $headers = []) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array_merge(['Accept: application/json', 'Cookie: ' . $cookie], $headers),
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($raw === false || $status < 200 || $status >= 300) {
throw new RuntimeException("HTTP $status: " . ($raw ?: curl_error($ch)));
}
curl_close($ch);
return $raw === '' ? null : json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
}
$projects = request('GET', $origin . $base . '/projects', $cookie);
print_r($projects);
$upload = request(
'POST',
$origin . $base . '/projects/upload',
$cookie,
['file' => new CURLFile('project.qgz', 'application/octet-stream', 'project.qgz')]
);
$id = rawurlencode($upload['id']);
$metadata = request(
'PUT',
"$origin$base/api/projects/$id/metadata",
$cookie,
json_encode(['title' => 'City map', 'description' => 'Published from PHP', 'tags' => []]),
['Content-Type: application/json']
);
print_r($metadata);
Download the current project without trying to decode it as JSON:
$ch = curl_init("$origin$base/projects/" . rawurlencode($id) . "/download");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Cookie: ' . $cookie],
CURLOPT_FILE => fopen('runtime.zip', 'wb'),
CURLOPT_FOLLOWLOCATION => true,
]);
curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {
throw new RuntimeException('Download failed');
}
curl_close($ch);
Notes¶
- Always send
Accept: application/jsontoGET /projects; an HTML accept header selects the browser redirect. - WMS/WFS, downloads, attachments, tiles, and previews may not be JSON.
- Quick-import, ingest action options, report definitions, and browser-map
map_configcontain payloads owned by adjacent packages/services. Their dynamic fields should be discovered from the corresponding UI/source rather than guessed. {BASE}/cache/and{BASE}/routing/are direct external-service proxies. The cache proxy itself has no Central ACL, even though project-management wrappers do.