Skip to content

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:

  1. Recompute the signature and compare in constant time (hmac.compare_digest).
  2. Reject the request if X-GSPAMJUDGE-Timestamp is 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.
  3. Deduplicate on data.id (one event, at least once) and on X-GSPAMJUDGE-Delivery (retries of one delivery).
  4. Answer 2xx quickly 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.