uclogserver
Log in

Admin

For the operators of the service. These endpoints need an administrator token (a token with the admin scope, created by a member of the administrators' account) and are not available in the sandbox. Administrators can also use every other endpoint on behalf of any account by adding ?customer=DOMAIN, e.g. GET /devices?customer=example.com.

Administrator actions on an account are recorded in the audit log under the administrator's identity; the account's own members see them as service administrator. Most of these functions are also available in the web console under Console → Admin.

List accounts

GET /admin/customers

Admin token

Every account (customer) with its current counts.

GET /admin/customers
curl "https://fw.unitcircle.ca/admin/customers" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/customers"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/customers",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {
      "domain": "acme.example",
      "name": "Acme Devices",
      "status": "active",
      "cors_origins": [],
      "is_sandbox": false,
      "created_at": "2026-09-26T01:06:38.275063Z",
      "updated_at": "2026-09-26T01:06:38.275063Z",
      "counts": {"firmware": 0, "logdata": 0, "devices": 0, "tags": 0, "active_tokens": 0, "stored_bytes": 0}
    }
  ]
}

Create an account

POST /admin/customers

Admin token

Creates a customer account identified by its domain. With owner_email, that person is invited as the account's first owner.

Body parameters (JSON)

domain string required

The customer's domain name, e.g. acme.example.

name string

Display name.

owner_email string

Invite this person as owner.

cors_origins array of strings

Browser origins allowed to call the API.

POST /admin/customers
curl -X POST "https://fw.unitcircle.ca/admin/customers" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"acme.example","name":"Acme Devices","owner_email":"owner@acme.example"}'
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/customers" \
  'domain=acme.example' \
  'name=Acme Devices' \
  'owner_email=owner@acme.example'
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/customers",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "domain": "acme.example",
        "name": "Acme Devices",
        "owner_email": "owner@acme.example"
    },
)
print(r.status_code, r.json())
Response
{
  "customer": {
    "domain": "acme.example",
    "name": "Acme Devices",
    "status": "active",
    "cors_origins": [],
    "is_sandbox": false,
    "created_at": "2026-09-26T01:06:38.275063Z",
    "updated_at": "2026-09-26T01:06:38.275063Z"
  }
}

Retrieve an account

GET /admin/customers/{domain}

Admin token

The account and its members.

Path parameters

domain string required

Account domain.

GET /admin/customers/{domain}
curl "https://fw.unitcircle.ca/admin/customers/acme.example" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/customers/acme.example"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/customers/acme.example",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "customer": {
    "domain": "acme.example",
    "name": "Acme Devices",
    "status": "active",
    "cors_origins": [],
    "is_sandbox": false,
    "created_at": "2026-09-26T01:06:38.275063Z",
    "updated_at": "2026-09-26T01:06:38.275063Z"
  },
  "members": [
    {"email": "owner@acme.example", "domain": "acme.example", "role": "owner", "disabled": false,
     "created_at": "2026-09-26T01:06:38.279233Z", "last_login_at": null, "user_disabled": false}
  ]
}

Update an account

PATCH /admin/customers/{domain}

Admin token

Rename, suspend (all its tokens stop working and update checks answer 404), reactivate, or set its CORS origins.

Path parameters

domain string required

Account domain.

Body parameters (JSON)

name string

Display name.

status string

active or suspended.

domain string

A new domain for the account.

cors_origins array of strings

Origins such as https://app.acme.example.

PATCH /admin/customers/{domain}
curl -X PATCH "https://fw.unitcircle.ca/admin/customers/acme.example" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cors_origins":["https://app.acme.example"]}'
http -A bearer -a YOUR_ADMIN_TOKEN PATCH "https://fw.unitcircle.ca/admin/customers/acme.example" \
  'cors_origins:=["https://app.acme.example"]'
import requests

r = requests.patch(
    "https://fw.unitcircle.ca/admin/customers/acme.example",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "cors_origins": [
            "https://app.acme.example"
        ]
    },
)
print(r.status_code, r.json())
Response
{
  "customer": {
    "domain": "acme.example",
    "name": "Acme Devices",
    "status": "active",
    "cors_origins": ["https://app.acme.example"],
    "is_sandbox": false,
    "created_at": "2026-09-26T01:06:38.275063Z",
    "updated_at": "2026-09-26T01:06:38.380531Z"
  }
}

Delete an account

DELETE /admin/customers/{domain}

Admin token

Deletes the account and all its data. Repeat the domain in confirm.

Members of a single account are managed with the member endpoints plus ?customer=DOMAIN.

Path parameters

domain string required

Account domain.

Query parameters

confirm string required

The same domain again.

DELETE /admin/customers/{domain}
curl -X DELETE "https://fw.unitcircle.ca/admin/customers/acme.example?confirm=acme.example" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN DELETE "https://fw.unitcircle.ca/admin/customers/acme.example?confirm=acme.example"
import requests

r = requests.delete(
    "https://fw.unitcircle.ca/admin/customers/acme.example",
    params={
        "confirm": "acme.example"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.text)
Response
(no body)

List users

GET /admin/users

Admin token

Every person with access to any account, one entry per membership.

GET /admin/users
curl "https://fw.unitcircle.ca/admin/users" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/users"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/users",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {"email": "owner@acme.example", "domain": "acme.example", "role": "owner", "disabled": false,
     "created_at": "2026-09-26T01:06:38.279233Z", "last_login_at": null, "user_disabled": false}
  ]
}

Disable a user

PATCH /admin/users/{email}

Admin token

Disables (or re-enables) a person everywhere. Disabling ends all their sessions and revokes every token they created.

Path parameters

email string required

The person's email.

Body parameters (JSON)

disabled boolean required

true to disable, false to re-enable.

PATCH /admin/users/{email}
curl -X PATCH "https://fw.unitcircle.ca/admin/users/owner@acme.example" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"disabled":true}'
http -A bearer -a YOUR_ADMIN_TOKEN PATCH "https://fw.unitcircle.ca/admin/users/owner@acme.example" \
  'disabled:=true'
import requests

r = requests.patch(
    "https://fw.unitcircle.ca/admin/users/owner@acme.example",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "disabled": True
    },
)
print(r.status_code, r.json())
Response
{"disabled": true, "email": "owner@acme.example"}

Retrieve a usage summary for all accounts

GET /admin/stats/summary

Admin token

Daily totals across the service and totals per account.

(none) counts requests that couldn't be attributed to an account, such as unauthenticated requests with bad tokens.

Query parameters

from date

Default 30 days ago.

to date

Default now.

GET /admin/stats/summary
curl "https://fw.unitcircle.ca/admin/stats/summary?from=2026-09-25" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/stats/summary?from=2026-09-25"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/stats/summary",
    params={
        "from": "2026-09-25"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "from": "2026-09-25T00:00:00Z",
  "to": "2026-09-26T01:07:38.449078Z",
  "days": [
    {"day": "2026-09-25", "requests": 310, "errors": 0, "firmware_uploads": 3, "logdata_uploads": 3,
     "firmware_downloads": 1, "notes_downloads": 0, "logdata_downloads": 3, "update_checks": 22}
  ],
  "customers": [
    {"domain": "sandbox.example", "requests": 112, "errors": 0, "update_checks": 25},
    {"domain": "example.com", "requests": 92, "errors": 0, "update_checks": 2},
    {"domain": "(none)", "requests": 122, "errors": 0, "update_checks": 2}
  ]
}

List requests for all accounts

GET /admin/stats/events

Admin token

The request log with full IP addresses and user agents.

Query parameters

domain string

Only this account.

ip string

Only this client address.

route string

Only this route pattern.

status integer

Only this status.

from date

Default 7 days ago.

to date

Default now.

limit integer

1–1000, default 100.

offset integer

Number of items to skip; use next_offset from the previous page.

GET /admin/stats/events
curl "https://fw.unitcircle.ca/admin/stats/events?domain=sandbox.example&limit=1" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/stats/events?domain=sandbox.example&limit=1"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/stats/events",
    params={
        "domain": "sandbox.example",
        "limit": "1"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {
      "ts": "2026-09-26T01:05:57.782977Z",
      "domain": "sandbox.example",
      "actor": "token:01a0dac8-6da2-77d7-8129-cea999b9a753",
      "method": "GET",
      "route": "/members",
      "path": "/members",
      "status": 403,
      "ip": "198.51.100.23",
      "user_agent": "curl/8.7.1",
      "bytes_in": 0,
      "bytes_out": 170,
      "duration_ms": 1,
      "request_id": "6249fb35-1a0b-47ef-93bc-21783f1c16af"
    }
  ],
  "next_offset": 1
}

List audit log entries

GET /admin/audit

Admin token

Every change made by people and tokens, with before and after values.

Query parameters

domain string

Only this account.

limit integer

1–1000, default 100.

GET /admin/audit
curl "https://fw.unitcircle.ca/admin/audit?limit=1" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/audit?limit=1"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/audit",
    params={
        "limit": "1"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {
      "ts": "2026-09-26T01:06:38.38274Z",
      "domain": "acme.example",
      "actor": "admin:token:01a0db39-6a51-7451-beae-61f769f9a88d",
      "action": "customer.update",
      "target_type": "customer",
      "target_id": "acme.example",
      "before": {"cors_origins": [], "…": "…"},
      "after": {"cors_origins": ["https://app.acme.example"], "…": "…"}
    }
  ]
}

List security events

GET /admin/security/events

Admin token

Findings of the suspicious-traffic detector (rate limiting, authentication failures, HW id enumeration, log-data scanning, tenant probing, traffic spikes, session-token reuse) and administrator logins. Critical events are also emailed to the operators.

Query parameters

kind string

e.g. hwid_enumeration.

severity string

info, warn or critical.

unacked boolean

true: only unacknowledged events.

limit integer

1–1000, default 100.

GET /admin/security/events
curl "https://fw.unitcircle.ca/admin/security/events?limit=1&unacked=true" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/security/events?limit=1&unacked=true"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/security/events",
    params={
        "limit": "1",
        "unacked": "true"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {
      "id": 9,
      "ts": "2026-09-26T01:05:57.416057Z",
      "kind": "admin_login",
      "severity": "info",
      "ip": "198.51.100.23",
      "domain": "unitcircle.ca",
      "detail": {"email": "admin@unitcircle.ca"},
      "notified_at": null,
      "acked_at": null,
      "acked_by": null
    }
  ]
}

Acknowledge a security event

POST /admin/security/events/{id}/ack

Admin token

Path parameters

id integer required

Event id.

POST /admin/security/events/{id}/ack
curl -X POST "https://fw.unitcircle.ca/admin/security/events/9/ack" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/security/events/9/ack"
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/security/events/9/ack",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.text)
Response
(no body)

List blocked addresses

GET /admin/ip-blocks

Admin token

Query parameters

all boolean

true: include expired blocks.

GET /admin/ip-blocks
curl "https://fw.unitcircle.ca/admin/ip-blocks" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/ip-blocks"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/ip-blocks",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {"cidr": "203.0.113.7/32", "reason": "scanning", "created_by": "admin:token:01a0db39-…",
     "created_at": "2026-09-26T01:06:38.540085Z", "expires_at": "2026-09-26T02:06:38.538189Z"}
  ]
}

Block an address

POST /admin/ip-blocks

Admin token

Requests from the address or network get 403 until the block expires or is removed.

Body parameters (JSON)

cidr string required

An IP address or CIDR network.

reason string

Shown in the console.

ttl_seconds integer

Expiry; omit for a permanent block.

POST /admin/ip-blocks
curl -X POST "https://fw.unitcircle.ca/admin/ip-blocks" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cidr":"203.0.113.7","reason":"scanning","ttl_seconds":3600}'
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/ip-blocks" \
  'cidr=203.0.113.7' \
  'reason=scanning' \
  'ttl_seconds:=3600'
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/ip-blocks",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "cidr": "203.0.113.7",
        "reason": "scanning",
        "ttl_seconds": 3600
    },
)
print(r.status_code, r.json())
Response
{"cidr": "203.0.113.7/32", "expires_at": "2026-09-26T02:06:38.538189Z", "reason": "scanning"}

Unblock an address

DELETE /admin/ip-blocks

Admin token

Query parameters

cidr string required

The blocked address or network.

DELETE /admin/ip-blocks
curl -X DELETE "https://fw.unitcircle.ca/admin/ip-blocks?cidr=203.0.113.7" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN DELETE "https://fw.unitcircle.ca/admin/ip-blocks?cidr=203.0.113.7"
import requests

r = requests.delete(
    "https://fw.unitcircle.ca/admin/ip-blocks",
    params={
        "cidr": "203.0.113.7"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.text)
Response
(no body)

Retrieve maintenance mode

GET /admin/settings/maintenance

Admin token

GET /admin/settings/maintenance
curl "https://fw.unitcircle.ca/admin/settings/maintenance" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/settings/maintenance"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/settings/maintenance",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{"enabled": false, "message": ""}

Set maintenance mode

PUT /admin/settings/maintenance

Admin token

While enabled, uploads and other changes answer 503 with Retry-After: 300; reads, update checks and downloads keep working.

Body parameters (JSON)

enabled boolean required

Turn maintenance mode on or off.

message string

Added to the 503 message.

PUT /admin/settings/maintenance
curl -X PUT "https://fw.unitcircle.ca/admin/settings/maintenance" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"message":"database upgrade, back at 14:00 UTC"}'
http -A bearer -a YOUR_ADMIN_TOKEN PUT "https://fw.unitcircle.ca/admin/settings/maintenance" \
  'enabled:=true' \
  'message=database upgrade, back at 14:00 UTC'
import requests

r = requests.put(
    "https://fw.unitcircle.ca/admin/settings/maintenance",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "enabled": True,
        "message": "database upgrade, back at 14:00 UTC"
    },
)
print(r.status_code, r.json())
Response
{"enabled": true, "message": "database upgrade, back at 14:00 UTC"}

List background jobs

GET /admin/jobs

Admin token

Backups, health roll-ups, retention clean-up and other scheduled work.

Query parameters

limit integer

1–1000, default 100.

GET /admin/jobs
curl "https://fw.unitcircle.ca/admin/jobs?limit=1" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/jobs?limit=1"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/jobs",
    params={
        "limit": "1"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "items": [
    {
      "id": "01a0db1e-a4e1-77fe-aadf-1493694023cb",
      "kind": "sandbox_reset",
      "payload": {},
      "slot": "sandbox_reset:2026-09-26",
      "status": "done",
      "run_at": "2026-09-26T00:30:13.729516Z",
      "attempts": 1,
      "max_attempts": 3,
      "last_error": null,
      "created_at": "2026-09-26T00:30:13.729172Z",
      "finished_at": "2026-09-26T00:30:13.833184Z"
    }
  ]
}

List backups

GET /admin/backups

Admin token

Backup, verification and restore runs, newest first. Backups run automatically every day.

Query parameters

limit integer

1–500, default 50.

GET /admin/backups
curl "https://fw.unitcircle.ca/admin/backups?limit=1" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/backups?limit=1"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/backups",
    params={
        "limit": "1"
    },
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "backend": "tarsnap",
  "items": [
    {
      "id": "01a0db40-1f61-7ed4-afbe-f550cce5d736",
      "kind": "backup",
      "status": "succeeded",
      "archive_name": "uclogserver-20260926T010647Z",
      "object_count": 16,
      "total_bytes": 29357,
      "initiated_by": "admin:token:01a0db39-…",
      "created_at": "2026-09-26T01:06:47.778537Z",
      "started_at": "2026-09-26T01:06:47.783098Z",
      "finished_at": "2026-09-26T01:06:48.044334Z",
      "error": null
    }
  ]
}

Start a backup

POST /admin/backups

Admin token

Queues a backup of the database and all files to the off-site backup service. Follow its progress with Retrieve a backup.

POST /admin/backups
curl -X POST "https://fw.unitcircle.ca/admin/backups" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/backups"
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/backups",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{"id": "01a0db40-1f61-7ed4-afbe-f550cce5d736", "kind": "backup", "status": "queued"}

Retrieve a backup

GET /admin/backups/{id}

Admin token

Status and log of a backup, verification or restore run.

Path parameters

id string required

Run id.

GET /admin/backups/{id}
curl "https://fw.unitcircle.ca/admin/backups/01a0db40-1f61-7ed4-afbe-f550cce5d736" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/admin/backups/01a0db40-1f61-7ed4-afbe-f550cce5d736"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/admin/backups/01a0db40-1f61-7ed4-afbe-f550cce5d736",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.json())
Response
{
  "id": "01a0db40-1f61-7ed4-afbe-f550cce5d736",
  "kind": "backup",
  "status": "succeeded",
  "archive_name": "uclogserver-20260926T010647Z",
  "object_count": 16,
  "total_bytes": 29357,
  "initiated_by": "admin:token:01a0db39-…",
  "created_at": "2026-09-26T01:06:47.778537Z",
  "started_at": "2026-09-26T01:06:47.783098Z",
  "finished_at": "2026-09-26T01:06:48.044334Z",
  "log": "01:06:47 backup uclogserver-20260926T010647Z — dumping database\n01:06:47 database dump: 93912 bytes\n01:06:47 exporting objects\n01:06:48 OBJECTS=16 BYTES=29357\n01:06:48 ARCHIVE=uclogserver-20260926T010647Z\n",
  "error": null
}

Verify a backup

POST /admin/backups/verify

Admin token

Queues a verification: the archive (default: the latest) is extracted and every file is checked against its manifest. The server only holds a write-only backup key, so pass a read-capable key (base64 of the key file); it is used for this run only and never stored.

Body parameters (JSON)

archive_name string

Default: the latest archive.

tarsnap_key_b64 string

A read-capable key, base64.

POST /admin/backups/verify
curl -X POST "https://fw.unitcircle.ca/admin/backups/verify" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"archive_name":"uclogserver-20260926T010647Z","tarsnap_key_b64":"\u003cbase64 of the read key\u003e"}'
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/backups/verify" \
  'archive_name=uclogserver-20260926T010647Z' \
  'tarsnap_key_b64=<base64 of the read key>'
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/backups/verify",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "archive_name": "uclogserver-20260926T010647Z",
        "tarsnap_key_b64": "\u003cbase64 of the read key\u003e"
    },
)
print(r.status_code, r.json())
Response
{"id": "01a0db41-0a2b-7c3d-9e8f-0123456789ab", "kind": "verify", "status": "queued"}

Report an external verification

POST /admin/backups/verify-report

Admin token

Record the result of a verification you ran elsewhere (for example on a machine that holds the full backup key), so it shows in the console and in /status.

Body parameters (JSON)

archive_name string required

The verified archive.

ok boolean required

Whether it passed.

detail string

Free text (becomes the log / error).

POST /admin/backups/verify-report
curl -X POST "https://fw.unitcircle.ca/admin/backups/verify-report" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"archive_name":"uclogserver-20260926T010647Z","detail":"restored into scratch DB; row counts match","ok":true}'
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/backups/verify-report" \
  'archive_name=uclogserver-20260926T010647Z' \
  'detail=restored into scratch DB; row counts match' \
  'ok:=true'
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/backups/verify-report",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "archive_name": "uclogserver-20260926T010647Z",
        "detail": "restored into scratch DB; row counts match",
        "ok": True
    },
)
print(r.status_code, r.json())
Response
{
  "id": "01a0db40-2b60-7bea-940d-b66cb9239cda",
  "kind": "verify",
  "status": "succeeded",
  "archive_name": "uclogserver-20260926T010647Z",
  "object_count": null,
  "total_bytes": null,
  "initiated_by": "admin:token:01a0db39-… (external)",
  "created_at": "2026-09-26T01:06:50.850387Z",
  "started_at": "2026-09-26T01:06:50.848779Z",
  "finished_at": "2026-09-26T01:06:50.848779Z",
  "log": "restored into scratch DB; row counts match",
  "error": null
}

Restore a backup

POST /admin/restores

Admin token

Replaces all current data with the archive. The service is in maintenance mode while the restore runs. Type the confirmation exactly as restore <archive_name>.

Body parameters (JSON)

archive_name string required

The archive to restore.

confirm string required

restore <archive_name>.

tarsnap_key_b64 string

A read-capable key (base64); required with tarsnap.

POST /admin/restores
curl -X POST "https://fw.unitcircle.ca/admin/restores" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"archive_name":"uclogserver-20260926T010647Z","confirm":"restore uclogserver-20260926T010647Z","tarsnap_key_b64":"\u003cbase64 of the read key\u003e"}'
http -A bearer -a YOUR_ADMIN_TOKEN POST "https://fw.unitcircle.ca/admin/restores" \
  'archive_name=uclogserver-20260926T010647Z' \
  'confirm=restore uclogserver-20260926T010647Z' \
  'tarsnap_key_b64=<base64 of the read key>'
import requests

r = requests.post(
    "https://fw.unitcircle.ca/admin/restores",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
    json={
        "archive_name": "uclogserver-20260926T010647Z",
        "confirm": "restore uclogserver-20260926T010647Z",
        "tarsnap_key_b64": "\u003cbase64 of the read key\u003e"
    },
)
print(r.status_code, r.json())
Response
{"id": "01a0db42-5d6e-7f80-9a1b-2c3d4e5f6a7b", "kind": "restore", "status": "queued"}