ThemysThemys/Docs

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.

ScopeEndpointsNotes ------------------------- extractPOST /api/extractRead-only text extraction from a URL analyzePOST /api/analyze, POST /api/analyze/batchFull risk analysis (LLM-backed, costs apply) monitorPOST /api/monitor, DELETE /api/monitor, GET /api/monitor, GET /api/monitor/alerts, PATCH /api/monitor/alertsSubscribe and be alerted on ToS changes

By 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
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

bash
curl -X GET https://themys.ca/api/developer/keys \

-H "Authorization: Bearer * \### Response

json

{

"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

}

]

}

code

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):

bash

curl -X DELETE https://themys.ca/api/developer/keys/ \

-H "Authorization: Bearer * \

code

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:

json

{

"stats": {

"totalRequests": 2547,

"successCount": 2519,

"errorCount": 28,

"successRate": 98.9,

"errorRate": 1.1,

"dailyRequests": [{ "date": "2026-07-01", "count": 412 }],

"recentErrors": []

}

}

code

GET /api/developer/usage/per-key

Per-key rate-limit snapshot — useful for "warning: my CI is about to hit the limit" UX:

json

{

"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"

}

code

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.

PlanPer-key daily scansPer-key monthly AI --------------------------- Free55 Base2530 Advanced100unlimited

When the per-key daily scans budget is exhausted, you'll get a 429 Too Many Requests payload like:

json

{

"error": "Daily scan limit reached for this API key",

"scansRemaining": 0,

"scansLimit": 25,

"scansResetsAt": 1720137600000

}

``

There is no per-key credit fallback — credits apply only to browser (Clerk) sessions. When your per-key budget runs out, the next call is at next-day UTC midnight reset, or you upgrade the parent user's plan.

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?