faucet
Request access
Guides

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

FieldRules
nameRequired. Non-empty, 60 characters or fewer
environmentlive or test. Defaults to live
model_allowlistArray of slugs, or null for every active model
rpm_limit, tpm_limitNon-negative integers, or null for the organization default
monthly_budget_microsA 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

RequestAnswers
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/key returns that key's budget, month-to-date spend and enforced limits — and nothing about your organization. GET /v1/credits reports the organization's balance, which under a reseller is your commercial information and is exactly what a subscriber must not be shown.
  • DELETE /v1/key revokes 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 answers 200 whether or not the key was already revoked — a changed field 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.