> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> The three ways to authenticate a request to the BlobHub REST API.

## Overview

The API supports three ways to authenticate: a bearer token, an API key, and a short-lived acting token
minted for a service account. All three are presented as a single request header — nothing else about a
request changes based on which one you use.

The examples below use [`/users/me`](/rest-api/conventions#the-me-alias) — see
[Conventions](/rest-api/conventions) for details on the `me` alias and other shared API
patterns.

### Bearer Token Authentication

In request headers:

```http theme={null}
GET /v1/users/me HTTP/1.1
Host: api.blobhub.io
Authorization: Bearer <your_token_here>
```

A bearer token comes from signing in — see [Google Sign-In](/rest-api/auth/google-signin) and
[Anonymous Sign-In](/rest-api/auth/anonymous-signin), refreshed via
[Refresh Token](/rest-api/auth/refresh-token) — and authenticates as the signed-in user directly, with
that user's full standing on everything they can reach.

### API Key Authentication

In request headers:

```http theme={null}
GET /v1/users/me HTTP/1.1
Host: api.blobhub.io
X-API-Key: <your_api_key_here>
```

An API key is minted for a **target** — an Organization, a Blob, or a User — and carries a **role**
(`read`, `write`, or `admin`) fixed at creation. A key minted at `target=user/{account}` authenticates
**as** that account: this is the only way to act as a [service account](/general/service-accounts). See
[Create API Key](/rest-api/shared/create-api-key) for how to mint one.

<Note>
  An API key never grants more than the role it was minted with, even when the user behind it holds a
  higher-role membership on the target being accessed. See [Role Cap](#role-cap) below.
</Note>

### Acting Token Authentication

In request headers:

```http theme={null}
GET /v1/users/me HTTP/1.1
Host: api.blobhub.io
Authorization: Bearer <acting_token_here>
```

Minted by [Impersonate](/rest-api/auth/impersonate), an acting token authenticates as a service account on
behalf of the human who minted it — presented exactly like a bearer token, not a separate header. It lasts
60 minutes, is single-shot (minting one issues no refresh token), and cannot be revoked early: it is a
stateless JWT, so the 60-minute expiry is the only control there is. See
[Impersonate](/rest-api/auth/impersonate) for who can mint one.

Because it carries the acting human, it is admitted where a service account's own key is refused, with one
exception — an org- or blob-scoped API key is not minted while acting, since it would record the impersonated
account rather than the human (`400 cannot_mint_while_acting`; `target=user` is admitted, and is what acting
mode is for). See [The Access Perimeter](#the-access-perimeter) below.

## The Access Perimeter

One question separates the three credentials above: **is there a human behind this request?** A bearer token
is the human. An acting token names the human who minted it. An API key answers with whoever its `user_id`
resolves to — a human for the ordinary org, blob and personal keys, and nobody at all when that `user_id` is
a service account. That distinction decides one thing, across ten operations.

A service account authenticated by its own key may do **product work** — read and write Blobs, commit
Revisions, run workflows, post into threads — at whatever role its memberships give it. It may not change
**who can access what**. Every operation that would receives `403` with `error: forbidden`, regardless of the
role the key was minted with, including `admin`:

| What it changes              | Endpoint                                                                                                          |
| :--------------------------- | :---------------------------------------------------------------------------------------------------------------- |
| Mint or revoke an API key    | [`POST`](/rest-api/shared/create-api-key) and [`DELETE`](/rest-api/shared/delete-api-key) on `/api-keys`          |
| Store or delete a credential | [`POST`](/rest-api/shared/create-credential) and [`DELETE`](/rest-api/shared/delete-credential) on `/credentials` |
| Grant or revoke a membership | [`POST`](/rest-api/shared/add-member) and [`DELETE`](/rest-api/shared/delete-member) on `/members`                |
| Create or retire an account  | [`POST`](/rest-api/users/create-account) and [`DELETE`](/rest-api/users/delete-user) on `/users`                  |
| Mint an acting token         | [`POST /auth/core/impersonate`](/rest-api/auth/impersonate)                                                       |
| Change `visibility`          | [`PATCH /orgs/:id`](/rest-api/orgs/update-org) and [`PATCH /blobs/:org_id/:blob_id`](/rest-api/blobs/update-blob) |

Reads are never governed — listing members, keys or credentials is ordinary work. The two `PATCH` endpoints
are governed only on the `visibility` field, and only when it actually changes; every other field, and a
`visibility` that matches what is already stored, goes through untouched. Human callers are unaffected
throughout, whichever of the three credentials they use.

Where automation genuinely has to cross the perimeter, mint an
[acting token](/rest-api/auth/impersonate) — that is what it is for. Full model:
[Service Accounts → The Access Perimeter](/general/service-accounts#the-access-perimeter).

## Role Cap

An API key's effective role on a target is the **lesser** of two things: the role it was minted with, and
the role its user holds there through membership or ownership. Holding an `admin` membership on an
Organization does not let a `read`-scoped key act as `admin` there — the key caps the role no matter how
much standing its user separately has.

A bearer token and an acting token are unaffected: both carry the user's full standing directly, with no
minted credential in between to cap.

<Note>
  This closes a real gap: before this change, the membership role won, so a narrowly-scoped key could reach
  further than intended by riding its user's broader membership. Measured against production before the fix
  shipped, no existing key was affected.
</Note>
