Endpoints
Every endpoint Faucet serves, which credential authenticates it, and what it answers with.
Everything below is reachable at https://api.intfaucet.com. Two endpoints are
public; the rest take a key in Authorization: Bearer.
Inference
| Endpoint | Notes |
|---|---|
POST /v1/chat/completions | Streaming and non-streaming, every chat provider. See Chat |
POST /v1/messages | Anthropic's native format. anthropic/* chat models only |
POST /v1/messages/count_tokens | Anthropic's native token count. Admitted and metered at zero — the one deliberately free operation |
POST /v1/embeddings | Never streamed. See Embeddings |
POST /v1/images/generations | Never streamed. See Images |
POST /v1/images/edits | JSON or multipart/form-data |
POST /v1/audio/transcriptions | multipart/form-data only. See Transcription |
A model answers on one endpoint. Sending it to another is a 400 naming the
endpoint that would have worked — not a 404, because the slug is fine. See
Choosing a model.
Catalog
| Endpoint | Notes |
|---|---|
GET /v1/models | Your catalog, filtered to what this key can reach. Exactly OpenAI-shaped |
GET /v1/models/{id} | Accepts a slug or an alias and echoes the canonical slug, plus a faucet capability block |
GET /v1/models is narrower than the Models reference:
it applies the key's allowlist and skips providers holding no usable credential,
so it never advertises a model that would 403 or 503 on use.
The detail route answers 404 when the model is not carried, is outside your
key's allowlist, or has no usable credential — one answer for all three, because
distinguishing them would confirm which model ids exist. What the faucet block
contains is on Model capabilities.
Money and credentials
| Endpoint | Credential | Notes |
|---|---|---|
GET /v1/credits | inference key | The organization's live credit. available is what the next request is judged against |
GET /v1/key | inference key | What the presenting key is allowed, and its own spend this month |
DELETE /v1/key | inference key | Revoke the presenting key. Names no id; idempotent |
POST /v1/management/keys | management key | Mint an inference key |
GET /v1/management/keys | management key | This organization's keys |
GET /v1/management/keys/{id} | management key | Its limits, plus spend_this_month_micros |
PATCH /v1/management/keys/{id} | management key | Change a plan |
DELETE /v1/management/keys/{id} | management key | Revoke |
Neither credential works where the other does
A management key (fct_mgmt_…) 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. See
Management keys.
/v1/credits and /v1/key are not rate-limited or metered. A key with
nothing left must still be able to ask what it is allowed — admitting there
would refuse the one request whose purpose is explaining a refusal.
The two routes answer different questions, and the difference matters under a
reseller: /v1/credits reports the organization's balance, which under a
reseller holds every subscriber and is the reseller's commercial information.
/v1/key reports only the presenting key's own budget and spend, which is what a
subscriber may see. Both are scoped to the key that calls them, so the worst a
stolen key learns is the allowance of the credential the thief already has.
Public, unauthenticated
| Endpoint | Notes |
|---|---|
GET /health | That this process is alive. A constant; touches no database |
GET /status | Whether the gateway is working, per provider |
These are not interchangeable. /health is what liveness probes watch — a
liveness check that read the database would turn a database blip into a restart
loop. /status reads recent traffic, so it can be slow or fail without anything
being restarted.
/status is the only endpoint that answers cross-origin, and deliberately: its
payload is public, identical for every caller, and sent with no credentials.
Nothing under /v1 is callable from a browser, because a Faucet key is a
server-side credential.
{
"status": "ok", // or "degraded"
"windowMinutes": 30,
"providers": [
{
"slug": "anthropic",
"status": "ok", // ok · degraded · unknown
"sample": "normal", // none · low · normal
"reportedStatus": "operational",
"statusPage": "https://status.anthropic.com"
}
]
}| Field | Meaning |
|---|---|
status | ok or degraded, across every provider |
windowMinutes | How far back the observed verdict looks |
providers[].status | ok, degraded or unknown for one provider |
providers[].sample | none, low or normal — how much traffic the verdict rests on |
providers[].reportedStatus | The provider's own published report, or null |
providers[].statusPage | Where to read that report yourself, or null |
Three things about that payload:
- It never reports request counts or model slugs.
samplebuckets how much traffic a verdict rests on without saying how much;lowis what stops you trusting adegradedoff a handful of requests. - Observed health wins. A provider's own report changes the aggregate only
when the observed sample is empty. A failed or unreadable provider feed is
null, never a degradation. unknownalone is not degraded. The top-levelstatusisdegradedwhen a provider is observed degraded, or isunknownand independently reports a problem.
Errors
Errors follow the dialect of the endpoint: Anthropic's envelope on
/v1/messages and its token-counting route, OpenAI's
{ "error": { "message", "type", "param", "code" } } everywhere else —
transcription included. Status meanings, the three 402 scopes and your
allowance are on
Errors and limits.