Management keys
Mint, inspect, re-plan and revoke inference keys from your own backend, for an app where each subscriber gets their own allowance.
If you are reselling Faucet — an app where each of your subscribers gets their
own allowance — you can issue and revoke their keys from your own backend
instead of the console. Ask us for a management key; it looks like
fct_mgmt_… and is not the same credential as the one your code sends
inference with.
A subscription product can be one key per subscriber: a monthly allowance, which models the plan reaches, an abuse ceiling and a kill switch are all fields on the key, and all are enforced at admission.
The two credentials are not interchangeable
A management key is refused at every inference endpoint, and an inference key
is refused on /v1/management/*. A key that leaks from a subscriber's machine
can spend its budget and cannot mint more.
Mint a key
curl https://api.intfaucet.com/v1/management/keys \
-H "Authorization: Bearer $FAUCET_MGMT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-8412" \
-d '{
"name": "subscriber-8412",
"environment": "live",
"monthly_budget_micros": "5000000",
"model_allowlist": ["anthropic/claude-opus-5"]
}'201, and the only response that ever contains the plaintext:
{
"object": "api_key",
"id": "key_01J…",
"key": "fct_live_…", // shown once — never returned again
"name": "subscriber-8412",
"environment": "live",
"key_prefix": "fct_live_",
"last4": "9f2c",
"model_allowlist": ["anthropic/claude-opus-5"],
"rpm_limit": null, // null = the organization default applies
"tpm_limit": null,
"monthly_budget_micros": "5000000",
"created_at": "2026-09-22T09:14:02.511Z",
"last_used_at": null,
"revoked_at": null
}Only the SHA-256 is stored, so hand key to your subscriber or your secret store
on receipt. A key that was not stored is reissued, never recovered.
What the body accepts
| Field | Rules |
|---|---|
name | Required. Non-empty, 60 characters or fewer |
environment | live or test. Defaults to live |
model_allowlist | Array of slugs, or null for every active model |
rpm_limit, tpm_limit | Non-negative integers, or null for the organization default |
monthly_budget_micros | A string of digits — micro-dollars. "5000000" is $5.00 |
Money is a string, and a number is refused
Not rounded — refused, at the door. A JSON number works for every amount anybody tests with and loses precision on the ones that matter, so the refusal cannot wait for the first value large enough to round.
Always send Idempotency-Key
If your sign-up request times out and you retry, it is what stops a second billed
key being created. A replay answers 409 carrying code key_already_created
and param idempotency_key, and names the key that already exists so you can
look it up or revoke it:
{
"error": {
"message": "A key for this `Idempotency-Key` was already created (key_01J…). Its secret was shown once and cannot be retrieved; revoke it and mint again if you no longer have it.",
"type": "invalid_request_error",
"code": "key_already_created",
"param": "idempotency_key"
}
}A 409 rather than a replayed 201, because the original response carried a
secret that exists nowhere else and cannot be reproduced. The 409 still does
the job it was sent for: no second key was minted.
Read, re-plan and revoke
| Request | Answers |
|---|---|
GET /v1/management/keys | { "object": "list", "data": [...] } — every key in this organization |
GET /v1/management/keys/{id} | One key, plus spend_this_month_micros |
PATCH /v1/management/keys/{id} | The updated key |
DELETE /v1/management/keys/{id} | { "object": "api_key", "id": …, "revoked": true } |
None of these ever returns the plaintext again. GET by id is the one that
adds a field — spend_this_month_micros, a live counter read from the same place
admission control judges against, not a settled figure.
The list is not paginated: it returns this organization's keys in one response.
PATCH leaves out what you leave out. Omitting monthly_budget_micros keeps the
current budget; sending null clears it. On a plan change that is the difference
between an edit and an accident:
curl -X PATCH https://api.intfaucet.com/v1/management/keys/key_01J… \
-H "Authorization: Bearer $FAUCET_MGMT_KEY" \
-H "Content-Type: application/json" \
-d '{ "monthly_budget_micros": "20000000" }'DELETE answers 404 on a second call — there, you are naming some other key
by id, and a repeat means that id is not yours or not there. (The subscriber-side
DELETE /v1/key behaves differently on purpose; see below.)
A key id that is not yours is a 404
Not a 403. A 403 would confirm that the id exists, which is the fact a caller
enumerating ids is trying to establish. One answer covers both "not yours" and
"not there":
{
"error": {
"message": "No such key: `key_01J…`.",
"type": "not_found_error",
"code": "key_not_found",
"param": "id"
}
}Limits only ever tighten
A key's limits narrow your organization's; they never widen them. Setting
rpm_limit above your organization default changes nothing, and raising a
ceiling means raising the default, which is a conversation with us.
What a subscriber can do without your management key
A key minted this way can read its own allowance and switch itself off, holding no second credential:
GET /v1/keyreturns that key's budget, month-to-date spend and enforced limits — and nothing about your organization.GET /v1/creditsreports the organization's balance, which under a reseller is your commercial information and is exactly what a subscriber must not be shown.DELETE /v1/keyrevokes the calling key. It is what a "sign out of this device" button should call: forgetting a key locally leaves it spendable by whoever finds it. It answers200whether or not the key was already revoked — achangedfield says which — because signing out twice is two clicks, not an error.
Both are safe by construction: they are scoped to the presenting key, so the worst a thief learns is the allowance of the credential they already stole. See Errors and limits.