Skip to content
GitLab

Middleware & Policies

Rate limits are enforced per-tenant using a Redis sliding window counter.

ScopeWindowLimit
Authenticated requests60 seconds300 per tenant
Unauthenticated requests60 seconds30 per IP

Rate limit headers are included on every authenticated response:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 285
X-RateLimit-Reset: 1738900860

When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating seconds until the next window.

Every request receives a unique request ID for distributed tracing:

  • If the client sends an X-Request-Id header, the server uses that value
  • Otherwise, the server generates req_{ULID} (e.g., req_01ARZ3NDEKTSV4RRFFQ69G5FAV)
  • The request ID appears in the response X-Request-Id header and in the instance field of all error responses

POST endpoints that create resources accept an optional Idempotency-Key header (UUID). Duplicate requests with the same key return the stored response without re-executing. Keys are stored for 24 hours.

Idempotency is scoped to the authenticated tenant: the cache is keyed on your tenant plus the key, so an Idempotency-Key you choose can never collide with another tenant’s. It is also fail-closed — requests with no authenticated tenant (public, guest, and webhook endpoints) are never cached at all, rather than sharing a common namespace. Those endpoints have their own replay protection: the Gitea webhook, for example, verifies an HMAC-SHA256 X-Gitea-Signature.

PUT, DELETE, and GET are naturally idempotent.

SettingValue
Allowed originsConfigurable via AKILI_CORS_ORIGINS env var
Allowed methodsGET, POST, PUT, PATCH, DELETE, OPTIONS
Allowed headersAuthorization, Content-Type, X-Request-Id, X-Tenant-Id
Exposed headersX-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Max age3600 seconds
Credentialstrue