# OAuth 2.0

OAuth lets a POS, accounting platform, or other backend application act for
companies that have explicitly approved it.

The model has three separate parts:

- The client application authenticates with `client_id` and `client_secret`.
- A company administrator creates an explicit Grant for that application.
- A short-lived access token is issued for one Grant and its scopes.


Company hierarchy never creates permission automatically.

## Authorization flow

### 1. Start authorization

Redirect a company administrator to:

```text
https://api.cheqi.io/oauth2/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fyour-app.example%2Foauth%2Fcallback
  &scope=write_receipts%20read_stores
  &code_challenge=BASE64URL_SHA256_CODE_CHALLENGE
  &code_challenge_method=S256
  &state=RANDOM_UNGUESSABLE_VALUE
```

Generate a high-entropy `code_verifier`, derive its Base64URL-encoded SHA-256
`code_challenge`, and retain the verifier server-side until the code exchange.
Cheqi requires `code_challenge_method=S256`.

Validate `state` when the administrator returns. Cheqi shows the requested
permissions and companies, and creates an explicit Grant for each approval.

### 2. Receive the authorization code

Cheqi redirects to your registered callback:

```text
https://your-app.example/oauth/callback?code=AUTHORIZATION_CODE&state=STATE
```

The authorization code is short-lived and single-use. Do not store it as a
long-term credential or attempt to reuse it after a successful exchange.

### 3. Exchange the code

Send the code to `POST /oauth2/authorization-code/exchange`:

```bash
curl -X POST https://api.cheqi.io/oauth2/authorization-code/exchange \
  -H "Content-Type: application/json" \
  -d '{
    "code": "AUTHORIZATION_CODE",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code_verifier": "YOUR_CODE_VERIFIER"
  }'
```

Cheqi verifies the verifier against the original S256 challenge before
consuming the authorization code. A missing or incorrect verifier is rejected.

The response contains the approved Grant metadata:

```json
{
  "grants": [
    {
      "grant_id": "grant-uuid",
      "grant_version": 1,
      "issuer_company_id": "company-uuid",
      "company_legal_name": "Example Retail B.V.",
      "tax_id": "NL123456789B01",
      "scopes": ["write_receipts", "read_stores"],
      "store_ids": ["store-uuid-1", "store-uuid-2"],
      "store_access_mode": "SELECTED_STORES"
    }
  ]
}
```

Store the `grant_id`, Company ID, and returned `store_ids` with your mapping to
the customer. The Grant ID is not a secret; the client secret remains
server-side. A Grant with unrestricted store access may return an empty
`store_ids` list, meaning all active stores belonging to that Company are
eligible.

The authorization-code response provides identifiers. Use the Grant-details
endpoint below when the POS needs current Store names, codes, addresses, or
active status.

The Company identity included in this consent response describes what the
administrator approved. It is not a substitute for the `company_access` scope;
that scope controls later Company-information API operations.

## Request a company access token

Request a token for the Grant that represents the customer transaction:

```bash
curl -X POST https://api.cheqi.io/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "grant_id": "grant-uuid",
    "scope": "write_receipts"
  }'
```

Cheqi verifies the client credentials, Grant ownership and status, and the
requested scope. The Company is resolved from the Grant; the client cannot
choose an arbitrary Company ID.

```json
{
  "access_token": "opaque-access-token",
  "token_type": "bearer",
  "expires_in": 900,
  "scope": "write_receipts"
}
```

Cache the token by `grant_id` and scope until shortly before it expires. Then
request another token with the same Grant. This flow does not issue a refresh
token.

## Synchronize Company and Store details

After obtaining a Company-bound token, fetch the current mapping with:

```bash
curl https://api.cheqi.io/oauth2/grants/grant-uuid \
  -H "Authorization: Bearer opaque-access-token"
```

The response contains the current Company details, permitted active Stores,
Grant version, and scopes:

```json
{
  "grant_id": "grant-uuid",
  "grant_version": 2,
  "company": {
    "id": "company-uuid",
    "companyLegalName": "Example Retail B.V."
  },
  "stores": [
    {
      "id": "store-uuid-1",
      "storeName": "Amsterdam",
      "storeCode": "AMS-01",
      "isActive": true
    }
  ],
  "scopes": ["write_receipts", "read_stores"],
  "store_access_mode": "SELECTED_STORES"
}
```

This endpoint requires `read_stores`. Call it again when synchronizing store
changes; the access token and Grant must still be active.

## Why `grant_type` is present

The two exchanges use separate endpoints:

| Endpoint | Purpose |
|  --- | --- |
| `POST /oauth2/authorization-code/exchange` | Redeem the one-time code returned after administrator consent and receive Grant metadata. |
| `POST /oauth2/token` | Authenticate the application and receive a short-lived token for one approved Grant. |


`grant_type` is still required in the `/oauth2/token` request because it
identifies the `client_credentials` OAuth token flow. It is not a permission;
the Grant and requested scope provide authorization.

## Use the access token

Send the token as a bearer token for recipient resolution and receipt
submission:

```http
Authorization: Bearer opaque-access-token
```

Use the same token through the complete receipt flow. Cheqi validates the live
Grant, token scope, Company, Store ownership, Store restrictions, and active
status on each protected operation.

## Multiple companies

One client application can have Grants for thousands of companies. Keep one
client credential, but request a separate short-lived access token for each
Grant. A revoked Grant affects only that company; other Grants remain active.

Never infer authority from parent/child or franchise relationships, and never
let an untrusted request select a Company independently of its Grant.

## Scopes

The current scope names are:

| Scope | Allows |
|  --- | --- |
| `write_receipts` | Resolve recipients and issue receipts |
| `read_receipts` | Read receipts available to the company |
| `write_stores` | Create or update stores where supported |
| `read_stores` | Read the company's stores |
| `company_access` | Access Company-information API operations after authorization |
| `account_access` | Access approved account information |


## Security

- Keep `client_secret` only in a server-side secret manager.
- Never place it in browser code, mobile apps, terminals, URLs, or logs.
- Use TLS for every request.
- Validate OAuth `state` and the exact redirect URI.
- Rotate client credentials with overlapping active credentials.
- Treat `401` as an authentication failure and `403` as a Grant or scope decision.