# Authentication (https://docs-ecmrhub-v2.lucaspiera.com/docs/authentication)

> How an agent or program connects to a company account, with OAuth or an API key, and what each permission allows.

Every call to Hub acts for exactly one company. The company decides who may act for it and what
they may do. There are two ways to connect, and both show up in the dashboard as a
**connection** the company can change or disconnect.

| | OAuth connection | API key |
| --- | --- | --- |
| Use it for | Agents with a person nearby: the CLI, Claude, ChatGPT, other MCP clients; programs serving many companies (TMS) | Scripts, CI and server automation of one company |
| How it starts | A person signs in to Hub in the browser and approves the requested permissions | The company owner creates a key in the dashboard; the secret is shown once |
| Credential | Access token plus refresh token, per company | `ecmr_sk_...` secret |
| Approvals by a person | On by default (the person can switch it off for this connection) | Off by default (the owner can switch it on for this key) |
| Revoke | Dashboard, Connections | Dashboard, Connections or API keys |

> **In build (OAuth and dashboard):** OAuth sign-in, the Connections list and the per-connection approval switch are part of phase 1.
> API keys are available today. The default approval setting per kind of connection is still
> open and may change.

## OAuth sign-in [#oauth-sign-in]

Hub runs an OAuth 2.1 authorization server (Supabase Auth). Clients register themselves with
Dynamic Client Registration, so neither you nor the company needs to create a client by hand.
Metadata: `https://api-ecmr-dev.lucaspiera.com/.well-known/oauth-authorization-server/auth/v1`.

What the person sees:

1. The agent or program opens the Hub sign-in page in the browser.
2. The person signs in with their Hub account (the company owner, for a new connection).
3. The consent screen names the program and its redirect address, and lists the permissions.
   By default it grants reading documents, writing and issuing drafts and reading the address
   book; signing links and tracking only when the person ticks them.
4. A switch "Require my approval to issue, send and delete" is on by default.
5. After approval the program receives a token for this one company, with a refresh token.

The token is a JWT for the person who approved, bound to the OAuth client. Hub accepts it only
while the connection is active: after the company disconnects it, every call answers
`401 CONNECTION_REVOKED`.

### From the CLI [#from-the-cli]

```bash
ecmrhub login            # browser sign-in, OAuth 2.1 with PKCE, local callback on 127.0.0.1
ecmrhub whoami --json    # company, connection and permissions
ecmrhub logout           # forget the tokens of this profile
```

Profiles (`--profile`) keep several companies apart on one machine. `ecmrhub login --no-browser`
prints the sign-in address instead of opening a browser. On a machine without a browser (SSH, CI)
use an API key.
`whoami` reports how the CLI is connected (`auth`: `oauth`, `apikey`, `env-key` or `env-token`).

### From a chat client [#from-a-chat-client]

Add the Hub MCP server as a connector; the client runs the same sign-in. Steps per client:
[MCP & connectors](https://docs-ecmrhub-v2.lucaspiera.com/docs/mcp.md).

### From a program serving many companies [#from-a-program-serving-many-companies]

Register one OAuth client (Dynamic Client Registration, or ask us to create one). Each company
clicks "Connect with eCMR Hub" in your program, signs in to Hub and approves the permissions. You
receive a separate token with refresh for each company, and each company sees your program in its
dashboard and can disconnect it.

## API keys [#api-keys]

The company owner creates a key in the dashboard under API keys, names it, picks permissions and
an optional expiry. The secret `ecmr_sk_<key id>_<secret>` is shown once and stored nowhere in
Hub; only its last four characters stay visible. A company has at most 20 active keys.

Use the key through the CLI or the SDK; both sign in for you:

```bash
export ECMR_HUB_API_KEY=ecmr_sk_...
ecmrhub whoami --json
```

```ts
import { ECMR } from "@ecmrhub/sdk";

const hub = new ECMR({
  url: "https://api-ecmr-dev.lucaspiera.com",
  // public, from GET https://api-ecmr-dev.lucaspiera.com/functions/v1/api-keys/config
  anonKey: "<anon key>",
  apiKey: process.env.ECMR_HUB_API_KEY!,
});
```

Never print, log or commit a key, and do not pass it as a command-line argument (it stays in the
shell history). A revoked or expired key answers `401 API_KEY_INVALID`.

### Calling HTTP directly with a key [#calling-http-directly-with-a-key]

The key is a sign-in for a technical account. Exchange it for an access token, then call the API
with the token:

1. Read the public `anonKey`: `GET https://api-ecmr-dev.lucaspiera.com/functions/v1/api-keys/config`.
2. Split the key `ecmr_sk_<32 hex>_<43 chars>`. The 32 hex characters are the account id; write
   them as a UUID (8-4-4-4-12). The last 43 characters are the password.
3. Sign in:

```bash
curl -s "https://api-ecmr-dev.lucaspiera.com/auth/v1/token?grant_type=password" \
  -H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
  -d '{"email":"<account uuid>@api-keys.ecmr-one.invalid","password":"<43 chars>"}'
```

4. Send `apikey: $ANON_KEY` and `Authorization: Bearer <access_token>` with every request. The
   token expires; sign in again when you get `401`.

## Permissions [#permissions]

A connection or key has a subset of these permissions. Reading documents is always included.

| Permission | Allows |
| --- | --- |
| `documents:read` | List and read drafts and documents, PDFs, evidence |
| `documents:write` | Create, update and delete drafts; issue documents |
| `signing:links` | Create, renew, revoke and send signing links |
| `library:read` | Read the company address book: contractors, vehicles, templates |
| `library:write` | Change the address book |
| `tracking:manage` | Live tracking of a transport |

What a connection or key can never do, whatever its permissions:

- sign a document as a party (sender, carrier, consignee),
- invite or remove people, create or revoke API keys, change connections,
- approve an approval request,
- delete an account.

A missing permission answers `403`: `API_KEY_FORBIDDEN` for API keys, `CONNECTION_FORBIDDEN`
for OAuth connections. The
[operation catalog](https://docs-ecmrhub-v2.lucaspiera.com/docs/cli/operations.md) shows the permission of every operation.

## Headers [#headers]

Every HTTP call sends:

| Header | Value |
| --- | --- |
| `apikey` | The public anon key of Hub (not secret) |
| `Authorization` | `Bearer <access token>` from OAuth or from an API key sign-in |
| `x-ecmr-approval` | Only when repeating a call after approval: the approval id |
| `Idempotency-Key` | Where an endpoint asks for it: a stable key per logical write |

The company comes from the token. There is no header that selects a company.
