Authentication
Every management request carries an access token:
curl https://fileshub.zaions.com/api/public/v1/token \
-H "Authorization: Bearer fh_pat_XXXXXXXXXXXXXXXX"
The X-Access-Token: fh_pat_... header is accepted as a fallback when a bearer header is awkward to
set. Prefer the bearer header — and never put a --token-style value where a shell records it.
Get a token
Create one in the FilesHub admin under Access Tokens: give it a name, decide whether it covers all projects or a chosen set, and optionally set an expiry. The full token is shown on the token's detail page (owner only) and is stored encrypted, so you can re-read it later — but treat it like a password.
Introspect first
An agent's first call should confirm the token works and learn its scope:
curl https://fileshub.zaions.com/api/public/v1/token -H "Authorization: Bearer $TOKEN"
{
"data": {
"name": "Claude Code CLI",
"token_prefix": "fh_pat_XzTJC7PU",
"all_projects": true,
"can_manage_supabase": true,
"can_read_vault": true,
"can_reveal_vault": false,
"can_write_vault": false,
"can_read_supabase_tokens": false,
"can_read_ai_accounts": false,
"can_read_developer_accounts": false,
"expires_at": null,
"last_used_at": "2026-07-18T09:12:44+00:00",
"created_at": "2026-07-18T09:00:00+00:00"
}
}
When the token is scoped to specific projects, all_projects is false and a projects array lists exactly
what it may manage — id, public_id, name, slug per row.
projects is absent, not null, on an all-projects tokenNote the response above has no projects key at all. An all-projects token covers projects that do not
exist yet, so no list could describe it, and the field is omitted rather than sent as null or []. Test
with 'projects' in data (or data.projects !== undefined), never data.projects === null. Every other
field on this page is always present.
Four further booleans are separate, off-by-default axes, independent of the project scope:
| Flag | Gates |
|---|---|
can_manage_supabase | The Supabase project vault — account-wide, so the project scope cannot express it |
can_read_vault | The project vault: metadata, links, and which credentials exist — never a value |
can_read_developer_accounts | Developer accounts — the tokens that publish under your name (npm, Hugging Face, Docker Hub, …). Its own axis: neither can_reveal_vault nor can_read_ai_accounts grants it |
can_reveal_vault | The project vault's credential values, config-file bytes and .env blocks. Implies read |
can_read_supabase_tokens | A Supabase account's personal access token (sbp_…) — the whole account, not one project |
can_write_vault | Writing project credentials, and assigning accounts to projects. Implies read, not reveal — a caller populating a vault needs to see which fields are already set, and does not need every secret handed back |
can_read_ai_accounts | An AI provider account's key — account-wide, so it spends that account's balance and reaches every model on it |
Two imply can_read_vault: can_reveal_vault and can_write_vault. Nothing else implies anything —
each remaining flag grants only itself. In particular:
can_manage_supabasedoes not grantcan_read_supabase_tokens— one reaches a project's credentials, the other reaches the account that owns every project.can_reveal_vaultdoes not grantcan_read_ai_accountsorcan_read_developer_accounts. It is the strongest grant over one project's configuration, and neither an account-wide AI key nor a token that publishes packages under your name is that.can_read_ai_accountsandcan_read_developer_accountsdo not imply each other. Spending an AI balance and publishing a package are separate decisions.
A token missing one of these gets 403 TOKEN_PERMISSION_DENIED with details.required_scope, rather
than the anti-enumeration 404 used for resources — the scope is a property of your own token, so an
honest error is more useful than pretending the resource does not exist. The exact strings that field
returns are not uniform yet:
details.required_scope.
Auth errors
Every failure is 401 with a machine-readable code:
| Code | Meaning |
|---|---|
MISSING_ACCESS_TOKEN | No bearer / X-Access-Token header was sent. |
INVALID_ACCESS_TOKEN_FORMAT | The value is malformed — e.g. you sent an fh_live_ API key, which does not work here. |
INVALID_ACCESS_TOKEN | The token is well-formed but unknown. |
TOKEN_REVOKED | The token exists but is deactivated. Re-activate or mint a new one. |
TOKEN_EXPIRED | The token is past its expiry. |
Revoke
Turn a token inactive (or delete it) in the admin. The next request with it returns
TOKEN_REVOKED immediately — there is no cache to wait out.
Rate limit
The management plane is limited to 120 requests per minute per client. Beyond that you get a
standard 429.