Developer portal¶
The developer portal is the place for API work: keys, usage, the request log, limits, webhooks, docs and a
playground. It uses the same account, organization and projects as the app. In production it lives on its own
host (for example platform.askaniela.com, next to app.askaniela.com). On a single host it is served under
/platform.
Sign in once: the session cookie is shared between the app and the portal when the server sets a common cookie domain. Use the organization switcher in the top bar and the project switcher in the sidebar, just like in the app.
Pages¶
| Page | What it shows |
|---|---|
| Overview | Spend this month against the plan allowance, prepaid balance, API calls and cost for 30 days, a quickstart and the last calls |
| API keys | Every key in the organization (project, personal and organization keys). Create a key (the secret is shown once), rotate it with a grace period, set an IP allowlist and an expiry, or revoke it. See Authentication |
| Usage | Spend this month, allowance left, balance, and API calls, errors and cost per day, endpoint and key (7 or 30 days). Amounts are in US dollars |
| Request log | One row per API call: time (UTC), endpoint (route template), key, status, latency, cost and request ID. Filter by key, endpoint and status class |
| Limits | Spending quotas (organization, project, you), plan limits, rate limits per key and request size limits. All read-only |
| Webhooks | Organization webhook endpoints: signing secret, test event, delivery log and redelivery |
| Docs | Quickstart in curl, Python and JavaScript and the full API reference (generated from openapi.json) |
| Playground | Send a real POST /v1/judge request (optionally with a search query and market) with your session or a pasted API key; see the request, the response JSON, the status and the cost |
Request log and privacy¶
The request log stores only metadata: time, request ID, route template (for example
/v1/reports/{report_id}, never the raw path or query string), method, key ID, project, status, latency
and cost. It never stores request or response bodies, page content, headers or secrets. Entries are kept for
30 days. Calls that fail authentication are not logged (they have no organization).
Owners and admins see every call in the organization. Members see calls in the projects they can see.
Cost of a call¶
Every paid response carries X-Cost-USD: the net cost of this call in US dollars (amount reserved minus refunds
made within the same request, e.g. for rules or rendering that did not run). The header is absent when nothing was
charged. For POST /v1/jobs it is the amount reserved for the whole job; refunds for items that fail later are
made by the worker and are not part of the header (see GET /v1/usage). The request log, the usage page and the
playground show the same value.
Organization webhooks¶
Besides the per-job webhook_url (Webhooks and jobs), owners and admins can register up to 10
endpoints per organization (optionally limited to one project). Each finished job sends a job.finished event
to every matching endpoint:
POST /your/endpoint
Content-Type: application/json
X-GSPAMJUDGE-Event: job.finished
X-GSPAMJUDGE-Delivery: 5f0c… (unique per delivery; deduplicate on it)
X-GSPAMJUDGE-Timestamp: 1760371200
X-GSPAMJUDGE-Signature: sha256=<hex>
{"event": "job.finished", "data": {"event": "job.finished", "id": "job_…:job.finished", "job": {…}}}
Delivery is at least once: after a worker crash or restart the same event can be queued again (with a new
X-GSPAMJUDGE-Delivery). data.id is the event's idempotency key (job ID + event name) and stays the same for
every repeat, so deduplicate business actions on data.id.
The signature is HMAC-SHA256 of "{timestamp}.{body}" (the raw request body) with the endpoint's signing secret
(whsec_…, shown on the endpoint page). On receipt:
- Recompute the signature and compare in constant time (
hmac.compare_digest). - Reject the request if
X-GSPAMJUDGE-Timestampis more than 5 minutes away from your clock (replay window). Retries and redeliveries are signed with a fresh timestamp, so a 5-minute window is safe. - Deduplicate on
data.id(one event, at least once) and onX-GSPAMJUDGE-Delivery(retries of one delivery). - Answer
2xxquickly and do the work asynchronously.
The signing secret is derived on the server from the server's webhook secret and the endpoint ID; it is not stored.
It stays the same for the life of the endpoint. To change it, delete the endpoint and add it again (per-endpoint
rotation is planned). Endpoints must use https and a public host name.
Each delivery is attempted up to 3 times with backoff; "Redeliver" sends the stored event again with a new
delivery ID. The delivery log is kept for 30 days.
API: GET|POST /v1/webhooks, DELETE /v1/webhooks/{id}, GET /v1/webhooks/{id}/secret,
POST /v1/webhooks/{id}/test, GET /v1/webhooks/{id}/deliveries,
POST /v1/webhooks/deliveries/{id}/redeliver (session or JWT of an organization owner or admin).
Self-hosting¶
| Setting | Effect |
|---|---|
GSPAMJUDGE_PLATFORM_HOST |
Host that serves the portal at /. Empty: the portal is under /platform |
GSPAMJUDGE_APP_HOST |
Host of the app (the portal's "Back to the app" link) |
GSPAMJUDGE_AUTH_COOKIE_DOMAIN |
Shared session cookie domain, e.g. .askaniela.com |
GSPAMJUDGE_REQUESTLOG_DAYS |
Retention of the request log and the webhook delivery log (default 30) |
GSPAMJUDGE_WEBHOOK_SECRET |
Required for webhooks; endpoint secrets are derived from it |
With a platform host set, /platform/... on any other host redirects (308) to the platform host. On the platform
host /docs is the portal's docs page; the built-in Swagger UI stays on the app host. The API reference script
(Scalar) is served from the server itself (/panel/static), not from a CDN.