Skip to content

Examples

Examples assume Central is reachable at https://app.example.test with empty BASE_PATH, and that PostgreSQL-backed auth is enabled. Replace secrets with values from your environment. Keep session cookies and PATs out of source control.

curl

Health

curl -sS https://app.example.test/api/health
COOKIE_JAR=$(mktemp)
curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" \
  -X POST https://app.example.test/login \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'username=admin' \
  --data-urlencode 'password=change-me' \
  --data-urlencode 'next=/' \
  -o /dev/null -w '%{http_code}\n'

curl -sS -b "$COOKIE_JAR" https://app.example.test/api/me | jq .
curl -sS -b "$COOKIE_JAR" https://app.example.test/api/catalog | jq .
curl -sS -b "$COOKIE_JAR" https://app.example.test/api/portal/cards | jq .

Create, use, and revoke a PAT

# requires an existing session in $COOKIE_JAR
curl -sS -b "$COOKIE_JAR" \
  -H 'Content-Type: application/json' \
  -d '{"name":"ci","expires_at":"2030-01-01"}' \
  https://app.example.test/api/me/tokens | tee /tmp/pat.json

TOKEN=$(jq -r .secret /tmp/pat.json)
curl -sS -H "Authorization: Bearer $TOKEN" https://app.example.test/api/me | jq .

ID=$(jq -r .token.id /tmp/pat.json)
curl -sS -b "$COOKIE_JAR" -X DELETE "https://app.example.test/api/me/tokens/$ID" -w '%{http_code}\n'

Admin: create a portal card and upload a thumbnail

curl -sS -b "$COOKIE_JAR" -H 'Content-Type: application/json' \
  -d '{
    "title":"City basemap",
    "card_type":"map",
    "url":"/qgis-publish-service/maps/city",
    "category":"Maps",
    "is_public":true,
    "sort_order":10
  }' \
  https://app.example.test/api/admin/portal/cards | jq .

curl -sS -b "$COOKIE_JAR" \
  -F 'file=@./preview.png' \
  https://app.example.test/api/admin/portal/thumbnails | jq .

Logout

curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" \
  -X POST https://app.example.test/logout -o /dev/null -w '%{http_code}\n'

JavaScript (browser)

// Same-origin portal scripts should include credentials so acugis_session is sent.
async function loadPortal() {
  const me = await fetch('/api/me', { credentials: 'include' }).then((r) => r.json());
  const cards = await fetch('/api/portal/cards', { credentials: 'include' }).then((r) => r.json());
  const catalog = await fetch('/api/catalog', { credentials: 'include' }).then((r) => r.json());
  return { me, cards, catalog };
}

async function createToken(name) {
  const res = await fetch('/api/me/tokens', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name }),
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json(); // includes one-time secret
}

Form login from a browser still uses classic HTML form POST to /login (not JSON).

Python

import requests

BASE = "https://app.example.test"
s = requests.Session()

# Password login (same flow as acugis CLI)
r = s.post(
    f"{BASE}/login",
    data={"username": "admin", "password": "change-me", "next": "/"},
    allow_redirects=True,
    timeout=60,
)
r.raise_for_status()

me = s.get(f"{BASE}/api/me", timeout=60).json()
assert me.get("authenticated"), me

catalog = s.get(f"{BASE}/api/catalog", timeout=60).json()
cards = s.get(f"{BASE}/api/portal/cards", timeout=60).json()

created = s.post(
    f"{BASE}/api/me/tokens",
    json={"name": "notebook", "expires_at": "2030-12-31"},
    timeout=60,
).json()
token = created["secret"]

# PAT-only client
pat = requests.Session()
pat.headers["Authorization"] = f"Bearer {token}"
print(pat.get(f"{BASE}/api/me", timeout=60).json())

# Admin group list (requires admin session)
groups = s.get(f"{BASE}/api/admin/groups", timeout=60)
groups.raise_for_status()
print(groups.json())

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/http/cookiejar"
    "net/url"
    "strings"
)

func main() {
    jar, _ := cookiejar.New(nil)
    client := &http.Client{Jar: jar}

    form := url.Values{}
    form.Set("username", "admin")
    form.Set("password", "change-me")
    form.Set("next", "/")
    resp, err := client.Post(
        "https://app.example.test/login",
        "application/x-www-form-urlencoded",
        strings.NewReader(form.Encode()),
    )
    if err != nil {
        panic(err)
    }
    io.Copy(io.Discard, resp.Body)
    resp.Body.Close()

    meResp, err := client.Get("https://app.example.test/api/me")
    if err != nil {
        panic(err)
    }
    defer meResp.Body.Close()
    var me map[string]any
    json.NewDecoder(meResp.Body).Decode(&me)
    fmt.Println(me)

    body, _ := json.Marshal(map[string]any{
        "title":     "Ops dashboard",
        "card_type": "dashboard",
        "url":       "/dashboard/",
        "is_public": true,
    })
    req, _ := http.NewRequest(http.MethodPost, "https://app.example.test/api/admin/portal/cards", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    cardResp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer cardResp.Body.Close()
    fmt.Println(cardResp.Status)
}

PHP

<?php
$base = 'https://app.example.test';
$cookieFile = tempnam(sys_get_temp_dir(), 'central');

function req($method, $url, $cookieFile, $body = null, $headers = []) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_COOKIEJAR => $cookieFile,
        CURLOPT_COOKIEFILE => $cookieFile,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_FOLLOWLOCATION => true,
    ]);
    $out = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$code, $out];
}

[$code, ] = req(
    'POST',
    $base . '/login',
    $cookieFile,
    http_build_query(['username' => 'admin', 'password' => 'change-me', 'next' => '/']),
    ['Content-Type: application/x-www-form-urlencoded']
);
echo "login HTTP $code\n";

[, $me] = req('GET', $base . '/api/me', $cookieFile);
echo $me, "\n";

[, $catalog] = req('GET', $base . '/api/catalog', $cookieFile);
echo $catalog, "\n";

// Bearer example after creating a PAT in Central Account UI or via /api/me/tokens
$token = getenv('ACUGIS_TOKEN');
if ($token) {
    $ch = curl_init($base . '/api/me');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
    ]);
    echo curl_exec($ch), "\n";
    curl_close($ch);
}

Common workflows

  1. Interactive operator: POST /login → browse /api/portal/cards and /api/catalog → open resource URLs.
  2. Automation: create PAT in Account UI or POST /api/me/tokens → call APIs with Authorization: Bearer.
  3. Admin onboarding: create user → assign groups → set resource ACL matrix → optionally import discovery items as cards.
  4. CLI-oriented: use sibling acugis login / acugis whoami / acugis catalog (see CLI).