API Keys
Manage Themys API keys for programmatic access.
Overview
API keys let you call the Themys API from any environment — CI pipelines, scripts, MCP servers, your own apps. They are scoped, billing-attached to your account, and isolated from your browser usage so heavy programmatic callers don't burn your plan's free-tier scans.
The full quick-start is on the API Overview. This page is the key management reference.
Generating a key
1. Sign in at themys.ca
2. Visit Developer Portal
3. Click Create API Key
4. (Optional) Set scopes — defaults to {extract, analyze} so new keys work for the full API surface out of the box
5. Save the raw key somewhere safe. It is shown once and never again.
Keys are 53 characters and start with tmys_ followed by 48 base64url characters. Example: tmys_bXyeTlRyVHpmZ3BwS0hOSEtGUlpxYjBfakt0LVE.
Security and storage
- Treat raw keys like passwords. Anyone with the raw key can spend your rate budget and make calls billed to your account.
- We store a SHA-256 hash, not the raw key. The hash is what gets looked up on each request. Even with full table access, attackers cannot recover raw keys — they would need to brute-force 2^192 of search space.
- Never commit keys to source control. Use environment variables (
THEMYS_API_KEY) or a secret manager. - Rotate periodically. Treat API keys like OAuth secrets. Generate a new one, update your environment, then revoke the old one.
- Watch your usage logs. The Developer Portal usage dashboard shows you every authenticated call.
Scopes
Scopes gate which endpoints a key is allowed to call. Pick the minimum set each key needs.
extractPOST /api/extractanalyzePOST /api/analyze, POST /api/analyze/batchmonitorPOST /api/monitor, DELETE /api/monitor, GET /api/monitor, GET /api/monitor/alerts, PATCH /api/monitor/alertsBy default a new key receives {extract, analyze}. Add monitor only for keys used by automated monitoring workflows.
Error: missing scope
If a key calls an endpoint it isn't scoped to, the API returns:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "API key missing required scope: analyze",
"requiredScope": "analyze"
}
Rotate or re-create the key with the missing scope to fix.
Listing keys
curl -X GET https://themys.ca/api/developer/keys \
-H "Authorization: Bearer * \### Response
{
"keys": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "CI Pipeline - Vendor onboarding",
"keyPrefix": "tmys_bXyeTlRy",
"scopes": ["extract", "analyze"],
"requestCount": 2547,
"lastUsedAt": "2026-07-04T14:23:18.000Z",
"createdAt": "2026-06-12T09:01:42.000Z",
"revokedAt": null
}
]
}
keyPrefix is the first 12 characters of the raw key — enough to identify which key is which when looking at your local secret store.
Revoking a key
To invalidate a key (logout-style):
curl -X DELETE https://themys.ca/api/developer/keys/
-H "Authorization: Bearer * \
Revocation is soft. The row stays in the database with revokedAt set for audit trail — it can no longer authenticate, and the developer portal stops showing it. If you want a clean hard delete, contact support.
Usage tracking
Each authenticated request increments the key's request_count (best-effort, async). The Developer Portal → Usage tab visualizes this. For headless dashboards, two endpoints return programmatic data:
GET /api/developer/usage
Per-user aggregate over the configured window:
{
"stats": {
"totalRequests": 2547,
"successCount": 2519,
"errorCount": 28,
"successRate": 98.9,
"errorRate": 1.1,
"dailyRequests": [{ "date": "2026-07-01", "count": 412 }],
"recentErrors": []
}
}
GET /api/developer/usage/per-key
Per-key rate-limit snapshot — useful for "warning: my CI is about to hit the limit" UX:
{
"keys": [
{
"keyId": "550e8400-e29b-41d4-a716-446655440000",
"keyPrefix": "tmys_bXyeTlRy",
"scopes": ["extract", "analyze"],
"status": {
"scans": {
"allowed": true,
"remaining": 18,
"limit": 25,
"resetsAt": 1720137600000,
"windowEnd": 1720137600000
},
"ai": {
"allowed": true,
"remaining": 22,
"limit": 30,
"resetsAt": 1722816000000,
"windowEnd": 1722816000000
}
}
}
],
"tier": "base"
}
resetsAt and windowEnd are Unix milliseconds (UTC). Compute human-readable durations client-side.
Rate limits (per key)
Each API key has its own rate limit pool, separate from the parent user's browser pool. So your CI pipeline can hammer /api/analyze/batch for hours without affecting your in-app scan history quota.
Plan Per-key daily scans Per-key monthly AI
------ ----------- ----------
Free 5 5
Base 25 30
Advanced 100 unlimited
When the per-key daily scans budget is exhausted, you'll get a 429 Too Many Requests payload like:
{
"error": "Daily scan limit reached for this API key",
"scansRemaining": 0,
"scansLimit": 25,
"scansResetsAt": 1720137600000
}
``
Tier for rate limits is taken from the parent user's publicMetadata.tier (set by Stripe webhook on plan change). When the parent upgrades, the next API call uses the new tier.
Rotating keys
A robust rotate flow:
1. Create a new API key in the portal (with the same scopes).
2. Update your environment / CI secret store to use the new key.
3. Verify a test request works.
4. Revoke the old key via DELETE /api/developer/keys/.
There is no "rotate atomically" endpoint — overlap is fine, both keys are valid until the old one is revoked. The portal also does not show you the raw key again — you must generate a new one to "rotate" in the literal sense.
Limitations (current v1)
- No per-key plan tier overrides. All keys pull from the parent user's plan limits. A free-tier account with API keys still gets free-tier per-key quotas. Splitting this requires a "developer plan" tier — on the roadmap.
- revokedAt
is not exposed in list responses yet.Portal UI hides revoked keys; the raw list shows them. Will be added in a future round. - No webhook subscriptions for usage thresholds. Poll /api/developer/usage/per-key` instead.
Was this page helpful?