API keys
Every request to the Finn API authenticates with an API key sent as a bearer token.
curl https://api.hirefinn.ai/v1/finns \
-H "Authorization: Bearer fk_live_YOUR_KEY_HERE"
A request with no Authorization header, a malformed header, or a revoked key fails with 401. A key that is valid but lacks the scope for the operation fails with 403. See api-errors for the full error shape.
Keys are created and revoked in the dashboard under Settings → API keys. There is no endpoint that creates keys, and there is no endpoint that lists key secrets. The secret is shown once, at creation time. If you lose it, you revoke that key and create another. There is no recovery path.
Key types
| Prefix | Environment | Notes |
|---|---|---|
fk_live_ | Production | Places real calls. Consumes wallet balance. |
fk_test_ | Sandbox | No telephony, no wallet spend. Use for local-development. |
A test key against a production resource returns 404, not 403. The two environments do not share data, so a Finn created with a test key is not visible to a live key.
Scopes
A key carries a fixed set of scopes chosen at creation. Scopes cannot be edited afterwards. To change what a key can do, create a new key and revoke the old one.
| Scope | Grants |
|---|---|
finns:read | Read agents and their configuration |
finns:write | Create, update, delete agents |
deployments:read | Read deployments and their status |
deployments:write | Create, start, stop deployments |
calls:read | Read call logs, transcripts, recordings |
calls:write | Place outbound calls |
audiences:read | Read audiences and contacts |
audiences:write | Create and modify audiences, add contacts |
numbers:read | Read phone number inventory |
numbers:write | Rent, release, reassign numbers |
webhooks:write | Create and modify webhook endpoints |
wallet:read | Read balance and transaction history |
Grant the smallest set that works. A key used by a reporting job that pulls transcripts nightly needs calls:read and nothing else. If that key leaks, the blast radius is read access to call data rather than the ability to place calls and drain the wallet.
Scopes gate the endpoint, not the row. A key with calls:read reads every call in the workspace. There is no per-Finn or per-deployment key scoping.
Rotation
Keys do not expire on their own. Rotation is manual, and the overlap window is what keeps it from causing an outage.
- Create the new key in Settings → API keys with the same scopes as the old one.
- Deploy the new secret to every consumer. Do not revoke yet.
- Watch the old key's Last used timestamp in the dashboard. It updates on every request.
- When the old key has gone quiet for longer than your slowest job's interval, revoke it.
Step 3 is the one people skip. A nightly cron that runs at 02:00 will look idle all afternoon. Revoking on the basis of six quiet hours breaks it that night, and the failure surfaces as 401 in a job nobody is watching. Wait out a full cycle of your longest-running scheduled work before revoking.
Revocation takes effect immediately. In-flight requests holding the old key fail. There is no grace period and no soft-delete, so a revoked key cannot be restored.
Storing keys
Keys are workspace-wide credentials. Anyone holding one acts with its scopes against your production data and your wallet balance.
- Keep keys in environment variables or a secrets manager, never in source control.
- Never call the Finn API from browser or mobile client code. The key ships to the user. Proxy through your own backend.
- Use a separate key per consumer (per service, per cron job, per integration) so you can revoke one without taking down the rest.
- Rotate immediately if a key appears in a log, a screenshot, a support ticket, or a public repository.
const res = await fetch("https://api.hirefinn.ai/v1/calls?limit=50", {
headers: {
Authorization: `Bearer ${process.env.FINN_API_KEY}`,
"Content-Type": "application/json",
},
});
if (res.status === 401) {
throw new Error("Key is invalid or revoked");
}
if (res.status === 403) {
throw new Error("Key is valid but missing a required scope");
}
const { data } = await res.json();
import os
import requests
res = requests.get(
"https://api.hirefinn.ai/v1/calls",
headers={"Authorization": f"Bearer {os.environ['FINN_API_KEY']}"},
params={"limit": 50},
timeout=30,
)
if res.status_code == 401:
raise RuntimeError("Key is invalid or revoked")
if res.status_code == 403:
raise RuntimeError("Key is valid but missing a required scope")
data = res.json()["data"]
Webhooks authenticate differently
Inbound webhook deliveries from Finn are not authenticated with your API key. They carry a signature computed from a separate signing secret, which is rotated independently. Do not reuse an API key as a webhook secret and do not treat a request as trusted because it names your workspace. See webhook-security.
Rate limits are per key
Limits apply per key, not per workspace, so splitting consumers across keys also splits their limits. A batch job that saturates its own key does not stall your production traffic. See api-rate-limits for the numbers and the retry behavior.
Related
- api-conventions — base URL, headers, request format
- api-errors — error codes and response shape
- webhook-security — signature verification for inbound deliveries
- compliance — what data a key can reach and how long it is retained