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

# SDK licences and permissions

> Configure API key permissions, download your SDK licence and verify it offline.

Your organisation can license **Player**, **Capture**, **Review** and **Report** separately. One licence covers all installations of that SDK. Production and staging share licences and quota. Sandbox API keys cannot retrieve or issue licences.

Every SDK accepts an **API key** and an optional **licence JWT**. Configure a production or staging key for your integration.

| SDK configuration | Licence acquisition |
| - | - |
| API key and a supplied `.jwt` | The SDK loads and verifies your supplied licence locally. It does not request a replacement simply because an API key is configured. |
| API key without a supplied `.jwt` | The SDK obtains its own licence through the CarHub API, using the permissions of that key. |

The API key remains the credential for permitted inference and job requests. The licence JWT proves the right to use the specified SDK; it never replaces API key authentication. Configure key secrets at runtime rather than committing them to your source code.

Licence scopes control API requests to retrieve, issue or renew proofs. If you supply a valid `.jwt`, local verification does not require a licence scope on the API key. Any inference, result or upload requests still require their own permissions.

## Configure API key permissions in the Hub

An organisation owner or administrator can create keys and change their permissions.

<Steps>
  <Step title="Open API access">
    Select your organisation in the Hub. Open **Api key & MCP**, which displays the **API access** page. Click **Create key**, or click **Permissions** beside an existing key.
  </Step>

  <Step title="Choose the environment and SDK">
    For a new key, select **Production** or **Staging**. Choose the **SDK licence** profile as a starting point.

    This profile initially selects **Manage licence** for **Capture** only. Set the SDK you integrate to **Manage licence** and leave other SDKs at **No access**. Use **Read licence** instead when the SDK only needs to retrieve an already issued licence. **Manage licence** is the recommended setting for automatic creation and renewal.

    | SDK setting | Scope | Allowed operations |
    | - | - | - |
    | No access | None | No licence access for this SDK |
    | Read licence | `sdk:{sdk}:read` | Retrieve the stored licence and its state |
    | Manage licence | `sdk:{sdk}:write` | Issue, renew, regenerate and retrieve the licence |

    Replace `{sdk}` with `player`, `capture`, `review` or `report`.
  </Step>

  <Step title="Add inference access only when needed">
    If your integration calls CarHub models, select the required **Inference endpoints**. Search by endpoint name, slug or family to find them.

    Under **Read results**, choose **Own jobs** to read jobs created by this key and its rotation predecessors. Choose **Organization jobs** only if you need all jobs in the key's live/test mode.

    Under **Uploads & storage**, enable **Prepare inference inputs** when you upload media for the selected endpoints. Leave **Storage inventory** at **No inventory access** unless your integration needs to inspect or delete stored uploads.

    | Example permission | What it allows |
    | - | - |
    | `inference:plate.read` | Call `plate.read` only |
    | `jobs:read:own` | Read this key's jobs and jobs from its rotation predecessors |
    | `jobs:read` | Read organisation jobs in the key's live/test mode |
    | `uploads:create` | Create input uploads for permitted inference endpoints |
    | `storage:read` | Inspect upload inventory and usage |
    | `storage:write` | Manage uploads, including deletion; also includes storage reads and input uploads |

    Search filters do not remove your existing selections. **Broad & legacy access** also provides a family search when there are many families. General and family access grant wider permissions than individual endpoint selections.
  </Step>

  <Step title="Review and save">
    Check **Access summary**. When editing a key, review **Changes to save**, then click **Save permissions**. When creating a key, click **Create key** and copy its secret immediately.

    Permission changes apply to new API requests. Key rotation preserves the permissions. Removing a licence permission does not invalidate previously issued licence proofs.
  </Step>
</Steps>

<Note>A new key with no permissions has no business access. The general `*` permission grants no SDK licence access. Each SDK needs its own explicit licence scope. A licence permission alone does not guarantee a paid entitlement or an available quota slot.</Note>

You can inspect your backend key's identity, organisation, environment, expiry and normalized permissions with `GET /v1/api-keys/self`. This endpoint is available to any valid key, even without business permissions. It never returns the secret and uses `Cache-Control: no-store`.

## Download a licence from the Hub

Sign in with your Hub account and select the correct organisation. Downloading through the Hub uses your user session. You still configure an API key in the SDK; providing the downloaded `.jwt` skips automatic licence acquisition.

<Steps>
  <Step title="Open SDK licences">
    Open **SDK licences** in the sidebar. Check the selected SDK's state, expiry and the organisation's consumed and available slots.
  </Step>

  <Step title="Issue or renew if needed">
    If the SDK has no licence, an owner or administrator can click **Issue**. For an existing licence, click **Renew for paid period** after payment for the next period is confirmed, even if the previous proof is still active. A licence already covering the paid period is returned unchanged.

    Successful issuance or renewal downloads the signed file automatically. It consumes one slot for that distinct SDK in the paid period. Repeating a current attribution consumes no additional slot.
  </Step>

  <Step title="Download an active licence">
    Click **Download licence** to retrieve the current proof. For Capture, the downloaded file is `capture.jwt`.

    Organisation members can download an existing licence. Issuing, renewing and regenerating require an owner or administrator.
  </Step>

  <Step title="Provide the file to your SDK">
    Supply the original JWT text from the file, together with the API key, in your SDK configuration. Configure the expected SDK, organisation, issuer and trusted public keys separately.

    Licence loading and storage depend on your SDK implementation. The CarHub API defines the proof format; it does not provide an SDK-specific import method.
  </Step>
</Steps>

**Regenerate** signs and downloads the current attribution again. It does not extend its expiry or consume another slot. Use **Renew for paid period** for the next confirmed paid period. Downloading an expired licence does not reactivate it.

Regeneration requires an active licence from the current paid period. If it returns `409 sdk_license_not_current`, issue or renew the licence first.

## Automatic acquisition by the SDK

If you do not supply a `.jwt`, the SDK obtains the licence itself. It can call `GET /v1/api-keys/self` to inspect the key's organisation, environment and normalized permissions. General `*` access is insufficient for licence acquisition.

| Key permission for the SDK | SDK request | Expected behaviour |
| - | - | - |
| `sdk:{sdk}:write` | `POST /v1/sdk-licenses/{sdk}` with `{}` | Issue or renew when required; otherwise return the existing current proof. Write includes read. |
| `sdk:{sdk}:read` only | `GET /v1/sdk-licenses/{sdk}` | Retrieve an existing proof without creating or renewing it. If no active licence exists, ask the user to issue or renew it in the Hub or grant management permission. |
| Neither permission | No authorised licence request | Report the missing SDK permission and direct the user to the key's **Permissions** in the Hub. |

For example, a Capture SDK configured with `sdk:capture:write` makes the equivalent of this request:

```bash theme={null}
curl --fail-with-body -sS "$CARHUB_API_URL/v1/sdk-licenses/capture" \
  -H "Authorization: Bearer $CARHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The SDK reads the signed JWT from `license.proof`, verifies it and retains it locally. It must not treat the complete JSON response as a JWT. The response also includes `state`, `max_sdks`, `consumed` and `available`. A successful GET can return an unassigned or expired licence; it does not guarantee a usable proof.

Use `GET /v1/sdk-licenses/capture` with read or write permission to retrieve the stored licence without renewal. Send `{"regenerate": true}` to the POST endpoint only when you want to sign the current attribution again.

All licence operations use the authenticated organisation. Do not supply an organisation identifier in the request. Licence proofs never authenticate calls to `/v1/*` or `/mcp`; use the API key for those calls.

## What the SDK must implement

### Load and retain the signed proof

Accept an API key in every SDK's configuration. Accept an optional JWT as a file or string. These are configuration requirements, not an SDK-specific method signature.

At initialization:

1. If the user supplied a JWT, verify that proof locally. Report an invalid or expired supplied proof; do not silently replace an explicit user-supplied licence.
2. If no JWT was supplied, reuse a valid locally cached proof for this SDK and organisation if one exists. Otherwise obtain the licence using the API key and the read/write behaviour above.
3. Verify any acquired proof before enabling licensed SDK operations. Cache the proof and trusted verification keys according to the platform.

The SDK performs automatic acquisition itself; the user does not need to run curl or implement a separate licence request. Protect the configured API key in the application's runtime and never include it in the downloaded `.jwt` file.

When replacing a proof, verify the new one first. Retain the previous valid proof if retrieval or verification fails. Do not log a complete proof or an API key secret.

### Verify locally before enabling licensed features

Retrieve public verification keys from `GET /v1/sdk-licenses/jwks` through a trusted connection before going offline. The response contains public Ed25519 keys with their `kid`. Select a trusted key by `kid`.

Validate all of the following:

* The JWT header uses `alg: EdDSA`, and the verification key uses Ed25519. Reject other algorithms.
* The signature verifies against a trusted public key. Do not accept a key or verification URL supplied by the JWT itself.
* `iss` exactly matches the issuer configured for your CarHub deployment.
* `aud` matches `carhub-sdk:{sdk}`, for example `carhub-sdk:capture`.
* `version` equals `1`.
* `sdk` matches the SDK you are running.
* `organization_id` matches your application's expected organisation.
* `license_id` matches `jti` and identifies the stable licence.
* `nbf <= iat < exp`, and the current time is within `[nbf, exp)`. Do not accept the proof before `nbf` or extend its validity past `exp`.

Configure the expected organisation and issuer independently of the incoming proof. Decoding claims without verifying the signature does not establish trust. Use a reliable local clock and validate dates when the licence is used, including in an application that remains open past expiry.

### Support verification key rotation

Keep trusted verification keys needed by unexpired proofs. When you receive an unknown `kid`, refresh keys through the trusted online path if available. If the required trusted key is unavailable offline, report that the licence cannot be verified. Never bypass verification.

### Handle expiry and renewal explicitly

An offline SDK can use a valid proof until its exact paid expiry. No mandatory online licence check exists. At expiry, disable the licensed operation and provide a clear path to load a renewed proof.

In automatic acquisition mode, the SDK can request renewal with the configured API key once the previous proof expires and payment for the next period is confirmed. Renewal requires `sdk:{sdk}:write`; read-only keys cannot renew. For a user-supplied `.jwt`, provide a clear way to load the renewed file downloaded from the Hub.

The backend does not renew licences automatically. Use bounded retries for network failures; do not repeatedly retry invalid keys, missing scopes, unpaid subscriptions or quota refusals. Keep a still-valid cached proof usable during network failures. At expiry, network failure does not extend its validity. Report expired licences, missing trusted keys and API acquisition errors distinctly.

## Quota and subscription changes

| Plan | Distinct SDKs per paid period |
| - | - |
| Trial | 0 |
| Starter | 1 |
| Scale | 3 |

Check `max_sdks` in `GET /v1/plans` for the current catalogue; existing contracts retain their version's terms. A licence covers every installation of its SDK.

At paid expiry, the period's consumed slots reset. Confirmed payment enables attribution for the new period. The first distinct SDKs requesting renewal obtain its slots. Cancellation preserves paid rights until expiry. A paid upgrade increases quota without resetting consumption. A scheduled downgrade applies to the next period's quota.

Revoking an API key blocks future requests through that key. It does not invalidate licence proofs already issued.

## Troubleshooting

| Error or state | Action |
| - | - |
| `401 invalid_api_key` | Check whether the key is expired, revoked or incorrect. |
| `403 scope_not_granted` | Open the key's **Permissions** and grant the explicit scope for the requested SDK or endpoint. |
| `403 license_environment_forbidden` | Use a production or staging key rather than a sandbox key. |
| `409 subscription_unpaid` | Confirm payment and the paid period in **Billing**. Scheduling a renewal is not sufficient. |
| `409 sdk_quota_exceeded` | Use an already attributed SDK or choose a plan with more SDK slots. Regeneration does not free a slot. |
| `409 sdk_license_not_current` | Issue or renew the licence for the current paid period before regenerating it. |
| `503 license_signing_unconfigured` | Licence issuance is unavailable for this deployment. Contact its operator. |
| Expired downloaded licence | Request renewal after payment confirmation and load the newly verified proof. |
| Unknown verification `kid` | Obtain the required public key through your trusted path before using the proof. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.