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

# Authentication

> Exchange a signed client assertion for a datasource-scoped access token.

Use the `client_id` and `key_fingerprint` from Admin → Connectors → Custom Source. Sign an RFC 7523 assertion with the matching private key. Copy the fingerprint into the JWT header as `kid`.

| Claim or header | Value                                                                                 |
| --------------- | ------------------------------------------------------------------------------------- |
| `alg` (header)  | `EdDSA` for Ed25519, or `ES256` for P-256                                             |
| `kid` (header)  | Key fingerprint shown in Admin                                                        |
| `iss`           | Client id: `<tenant_slug>/<datasource_name>`; exactly one `/`, at most 128 characters |
| `sub`           | Same as `iss`                                                                         |
| `aud`           | `https://app.teampascal.com/api/v1/custom-source/token`                               |
| `iat`, `exp`    | Integer seconds; `exp - iat ≤ 300`; 30 seconds of clock leeway                        |
| `jti`           | Unique string of at most 200 characters; replay within 10 minutes is rejected         |

Send `POST /token` with `Content-Type: application/x-www-form-urlencoded` and these fields:

```text theme={null}
 grant_type=client_credentials
 client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
 client_assertion=<JWT>
```

A successful response includes `Cache-Control: no-store`:

```json theme={null}
{"access_token": "<access_token>", "token_type": "Bearer", "expires_in": 3600}
```

Access tokens last 60 minutes and are bound to one datasource. Send `Authorization: Bearer <access_token>` on every data request. Reuse the token until renewal is needed; create a fresh assertion and `jti` for each exchange.

Token exchange allows one request per second per client id with a five-second burst. Exceeding this returns 429 `rate_limited`. Wrong form grant or assertion type returns 400 `invalid_request`.

Authentication failures return 401 `invalid_client` without further detail by design. Wrong key, `kid`, audience, expiry, replayed `jti`, unsupported algorithm, and a disabled connector all look the same. Check those inputs and your system clock. A 401 includes `WWW-Authenticate: Bearer`.

On a data-route 401 `invalid_token`, exchange a new token and continue with the same upload id. Token expiry or key rotation does not discard an upload. After rotation, use the new private key and fingerprint for the exchange.

## Create a connector

In **Admin → Connectors → Custom Source**, enter:

1. A datasource name matching `^[a-z][a-z0-9_]{2,40}$`.
2. Comma-separated document types from `purchase_order` or `invoice`.
3. Optionally, an Ed25519 or P-256 public key in SPKI PEM format, starting with `-----BEGIN PUBLIC KEY-----`.

The Credentials step shows `client_id` and `key_fingerprint`. If you leave the public key blank, Pascal generates an Ed25519 pair and shows the PKCS8 PEM `private_key` **once**. Pascal never stores that private key. Save it in your job's secret store before leaving the screen.

To generate an Ed25519 key locally:

```sh theme={null}
openssl genpkey -algorithm ed25519 -out custom-source.key
openssl pkey -in custom-source.key -pubout -out custom-source.pub
```

Paste the contents of `custom-source.pub` into the connector. Store `custom-source.key` privately, in your job's secret store.

For P-256, generate the key with this command, then use the same public-key export command above:

```sh theme={null}
openssl ecparam -name prime256v1 -genkey -noout -out custom-source.key
```

Use `ES256` to sign P-256 assertions and `EdDSA` for Ed25519. The fingerprint is `base64url(sha256(DER SubjectPublicKeyInfo))` without padding. Copy it from Admin instead of computing it yourself.

## Rotate the signing key

Each connector has one active key. To rotate it, update the connector with a new public key, then give your job the new private key and fingerprint. Old tokens return 401 `invalid_token`; in-flight uploads are unaffected.

Pascal cannot show a private key again after it is generated. If you lose one, rotate.
