Przejdź do treści

Authentication

API calls use an API key:

Authorization: Bearer gsj_live_<prefix>_<secret><checksum>

Keys created before #125 have the form gsj_<prefix>_<secret> and keep working.

Key owners

Every key belongs to one owner (owner_type). What a key can do is the intersection of its scopes and the owner's current permissions, so a key never gives more than its owner has.

owner_type Reaches Who creates it
project one project; reports created with the key land in it project owner, organization owner/admin
user what the user can access in one organization, right now (a viewer's key is read-only) the user (not a viewer)
org all projects of the organization organization owner/admin

A user key stops working as soon as its owner leaves the organization (it is revoked with reason owner_left). Keys are managed only with a user session or JWT. An API key cannot create, rotate or revoke keys, and a platform operator cannot read secrets or create keys on behalf of users.

Create, list, rotate, revoke

POST   /v1/keys                         {"name": "ci", "owner_type": "org", "scopes": ["judge", "reports:read"]}
POST   /v1/projects/{prj_…}/keys        {"name": "ci"}                      # project key
GET    /v1/keys                         # keys you may see in the current organization (metadata only)
GET    /v1/projects/{prj_…}/keys
POST   /v1/keys/{key_…}/rotate          {"grace_hours": 24}                 # new key; old one works for the grace period
DELETE /v1/keys/{key_…}                 # revoke now (also ends a grace period)
DELETE /v1/projects/{prj_…}/keys/{key_…}
  • The response to create or rotate contains key once. The server stores only a keyed hash (HMAC-SHA256), so nobody can show the secret again.
  • Optional fields when you create a key: scopes, rate_limit (requests per minute), allowed_ips, expires_at or expires_in_days, never_expires.
  • Lists show display (gsj_live_<prefix>_…), scopes, expires_at, allowed_ips, last_used_at, last_used_ip, revoked_at, revoke_reason (manual, rotated, leaked, owner_left) and rotated_from. When revoked_at is in the future, the key is in its rotation grace period.
  • Scopes: judge (/v1/judge, /v1/judge/batch), reports:read (/v1/reports, exports), jobs (/v1/jobs), projects:read (GET /v1/projects), feedback:write. Default: judge + reports:read.

Expiry and rotation

  • New keys expire after 90 days by default; you can choose up to 1 year. Only organization keys created by an organization owner/admin may use "never_expires": true.
  • Rotation creates a new key with the same name, scopes, limit, allowlist and owner. The old key keeps working for the grace period (default 7 days, grace_hours 0-720). After that it returns 401 invalid_api_key.
  • An expired key returns 401 api_key_expired.

IP allowlist

allowed_ips is a list of IPv4/IPv6 addresses or CIDR ranges (["203.0.113.0/24", "2001:db8::/32"], max 50). An empty list allows any address. A request from outside the list returns 403 ip_not_allowed. Behind a reverse proxy the server reads X-Forwarded-For only when the connection comes from an address in GSPAMJUDGE_TRUSTED_PROXIES (self-hosting). Otherwise it uses the connection address.

Leaked keys

If a key was published (a public repository, a paste or logs), report it:

POST /v1/keys/leaks   {"keys": ["gsj_live_…"], "source_url": "https://…"}    # no authentication

The server revokes each matching active key immediately (revoke_reason: leaked), notifies the organization owners by e-mail and writes an audit-log entry. The response is always 202 {"received": N}, so it does not reveal whether a key existed. Organization owners are also notified when a new key is created.

Key format for secret scanners

New keys match the following pattern. It is meant for GitHub secret scanning (partner program, registration pending), gitleaks, trufflehog and similar tools:

\bgsj_live_[0-9a-f]{12}_[0-9A-Za-z]{43}[0-9A-Za-z]{6}\b

The last 6 characters are a CRC32 checksum of the rest of the key in base62 (0-9A-Za-z, left-padded with 0). A scanner can use it to drop random strings that only look like keys, without calling the API.

Errors and limits

See Errors. Missing credentials: 401 unauthorized. Unknown, revoked or wrong key: 401 invalid_api_key. Expired key: 401 api_key_expired. Address outside the allowlist: 403 ip_not_allowed. Missing scope: 403 insufficient_scope. Rate limit: 429 rate_limited with Retry-After and RateLimit-* headers. The default limit is 60 requests per minute per key, and rate_limit overrides it for one key. With several server processes the limit is a fixed one-minute window shared in Redis, so up to twice the limit can pass around a minute boundary. If the limiter cannot be reached, the API answers 503 rate_limiter_unavailable.

Keep keys secret

  • Keep keys server-side. Never put them in browser code, URLs or logs. Use a separate key per integration, with the smallest scopes and an IP allowlist where you can.
  • Server logs never contain keys. Anything that matches gsj_…, the Authorization header value and secret query parameters are replaced with [redacted].
  • Send your own X-Request-ID to correlate logs. Every response echoes it.