API keys are AppEngine's answer to "I need a backend service to call the API without managing a JWT lifecycle". Keys are long-lived, scoped to a User principal, and sent on every request as the x-api-key header.
When to use a key vs a JWT
| Pattern | Use JWT | Use API key |
|---|---|---|
| Browser session | yes | no |
| Mobile app session | yes | no |
| Backend cron job | no | yes |
| Build pipeline | no | yes |
| Server-rendered Next.js calling AppEngine | usually JWT (per-user) | API key for shared/system data |
| Webhook receiver in your code calling back into AppEngine | yes (system user JWT) | yes |
JWTs encode a specific user session and expire. Keys are tenant-level credentials — they survive deploys, can be rotated without re-authenticating users, and carry their own rate limits.
Sending a key
GET /repository/find/contact HTTP/1.1
Host: appengine.appmint.io
orgid: my-org
x-api-key: ak_live_5f3a2b87e4c1...
orgid is still required. The JwtAuthGuard falls back to the ApikeyAuthGuard when no Bearer token is present.
Issuing a key
The Studio Manager UI is the easiest path: Account → API keys → "+ Create New Key". Programmatically:
/api-key/createJWTcurl -X POST https://appengine.appmint.io/api-key/create \
-H "orgid: my-org" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Web App",
"description": "Used by the Next.js server",
"permissions": ["write:profile", "write:data"],
"rateLimits": { "perMinute": 100, "perHour": 1000, "perDay": 10000 },
"expiresAt": null
}'
The response includes the generated key string under data.key. Save it immediately — AppEngine stores only a hash; you cannot retrieve the plaintext key again.
Keys grant whatever permissions they were issued with — and unlike a JWT, they don't expire on their own. A leaked key can read or write the entire org until you revoke it. Server-side only.
Listing your keys
/api-key/listJWTReturns metadata only (no plaintext) — name, description, scopes, rate limits, last-used timestamp. Use this to audit what's still active.
/api-key/{keyId}JWTSingle key by id, same metadata-only response.
Permissions and scopes
Each key has a permissions array. The strings follow the verb:resource pattern — read:data, write:data, read:profile, write:profile. AppEngine maps these onto the same permission verbs (create, read, update, delete) the role system uses; a key without write:data can't POST /repository/create regardless of which User backs it.
Common scope sets:
- Read-only export —
read:data,read:profile. Safe for analytics pipelines. - Storefront sync —
write:datascoped to specific collections (advanced setup, configured per-key). - Full backend — all verbs. The system-user pattern.
Rate limits
Per-key rate limits are enforced by UsageMiddleware. Defaults are 100/min, 1000/hour, 10000/day — generous for most server-to-server use, restrictive enough that a runaway loop won't take down your tenant.
The 429 response includes a Retry-After header. Honor it — repeated 429s within a short window count against your org's reliability score, which can affect rate-limit allowances over time.
Rotating a key
/api-key/regenerate/{keyId}JWTRotates in place: same name, same scopes, new key string. Use this for scheduled rotation. The old key continues working for a short grace period (configured per-org, default 24h) so you can deploy without downtime.
Revoking a key
/api-key/{keyId}JWTHard revoke. The key stops working immediately. Use this if you suspect a key is compromised.
For an admin acting on a key they didn't create:
/api-key/admin/revoke/{keyId}JWTRequires RootAdmin or ConfigAdmin. Logged in the org's audit trail.
Updating metadata
/api-key/{keyId}JWTUpdate the name, description, scopes, or rate limits. The key string itself doesn't change. To change the secret, use regenerate.
Usage
/api-key/usage/{keyId}JWTReturns request counts, error rates, and rate-limit hits over the configured window. Hook this into your monitoring.
Admin views
For ConfigAdmins managing org-wide keys:
/api-key/admin/listJWTCross-cuts all keys in the org, including ones created by other Users. The non-admin list endpoint only shows keys you created.
Per-org key endpoints
Some workflows (org provisioning, system-user setup) need a key tied to an org rather than a User. The org-scoped variants:
/api-key/org/{orgid}JWT/api-key/org/{orgid}/createJWT/api-key/org/{orgid}/deleteJWTThese are mostly used by the platform's own provisioning code. For application-level keys, use the User-scoped endpoints above.
Validating a key directly
Rare, but if you need to verify a key without making a real API call:
/api-key/authNo authReturns { valid: true|false, principal?: {...} }. Useful in custom proxies that want to enforce AppEngine auth before forwarding the request.
Pattern: system user
A widely-used pattern for legacy integrations: create a User record with a service-account email, give it the roles you need, and use its API key for backend calls. The key is then bound to that User's permissions, which makes audit trails clean ("contact updated by [email protected]") even though no human signed in.
When you create an org through the AppMint signup flow, a system User and a default API key are provisioned automatically. Find them in Studio Manager → Account → API keys.