# Authentication & keys

Every REST request and every MCP tool call is made on behalf of one workspace, with either an API key or an OAuth token. This page explains keys, modes, scopes and team roles, and how to keep keys safe.

## Send your key

Send the key as a Bearer token on every request:

```http
Authorization: Bearer au_test_...
```

`X-Api-Key: au_test_...` is also accepted, but prefer the `Authorization` header: it is what MCP clients and most HTTP tooling expect. Keys are never accepted in a URL or query string.

Keys have a fixed format: `au_test_` or `au_live_` followed by 43 URL-safe characters. A value that does not match this format is rejected with `401 invalid_api_key` before any lookup, so a key with extra spaces, quotes or a line break fails immediately. Check the configured secret first when you see that error.

Check a key at any time with the free [account endpoint](/docs/reference/account):

**curl**

```bash
curl https://data-services.autouncle.com/v1/account \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
require "json"
require "net/http"

uri = URI("https://data-services.autouncle.com/v1/account")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
```

**Python**

```python
import json
import os

import requests

response = requests.get(
    "https://data-services.autouncle.com/v1/account",
    headers={
        "Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
    },
    timeout=30,
)
print(json.dumps(response.json(), indent=2))
```

**JavaScript**

```javascript
const response = await fetch("https://data-services.autouncle.com/v1/account", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
  },
});
console.log(JSON.stringify(await response.json(), null, 2));
```

## Test and live modes

| | Test key (`au_test_`) | Live key (`au_live_`) |
| --- | --- | --- |
| Valuations | Deterministic, synthetic results. Always `indicative`, with a `synthetic_result` note. | Real market valuations from AutoUncle's models. |
| Credits | Never used. | Only on success: a `valued` valuation uses 1, plus 1 when requested comparables are returned and 1 when sent photos are used; a selling-time forecast uses 2; normalization and VIN, HSN/TSN and plate lookups use 1 when they return vehicle data. Refusals, failures and replays are free. See [credits](/docs/credits). |
| AI interpretation of text and photos | Never runs. | Runs only when it is configured for the deployment and needed. |
| Results | Readable only with test keys of the same workspace. | Readable only with live keys of the same workspace. |
| Idempotency keys | A separate namespace from live. | A separate namespace from test. |
| Concurrency | Its own per-workspace limit, so tests never block live traffic. | Workspace, market and platform limits. |
| Fair use | Valuations, selling-time forecasts, normalization and VIN, HSN/TSN and plate lookups share an allowance of 10 per minute, 100 per hour and 300 per day per workspace, on top of the per-minute rate limits. Past it: `429 test_limit_reached`. | None beyond the rate limits and credits. |

Both modes use the same endpoints, validation, scopes, per-minute rate limits and error codes, so code that works with a test key works with a live key. Swap the secret, not the code. Test keys also have the fair-use allowance above, so a test key is not a free production resolver. See [testing with test keys](/docs/guides/testing) and [limits](/docs/concepts/limits).

## Scopes

A key has one or more scopes. A call without the right scope fails with `403 insufficient_scope`.

| Scope | Allows | REST | MCP tool |
| --- | --- | --- | --- |
| `valuations:write` | Create valuations and selling-time forecasts | `POST /v1/valuations`, `POST /v1/selling-times` | `valuate_car`, `forecast_selling_time` |
| `valuations:read` | Read recorded valuations | `GET /v1/valuations/{id}` | `get_valuation` |
| `catalogue:read` | Browse the catalogue | `GET /v1/catalogue/...` | `search_vehicles` |
| `account:read` | Check the key, workspace and credits | `GET /v1/account` | `get_account` |
| `vehicles:read` | Identify vehicles: normalize vehicle facts, decode VINs and German HSN/TSN codes, look up registration plates | `POST /v1/vehicles/normalize`, `POST /v1/vehicles/decode`, `POST /v1/vehicles/hsn-tsn`, `POST /v1/vehicles/lookup` | `normalize_vehicle`, `decode_vin`, `decode_hsn_tsn`, `lookup_plate` |

New keys get all five scopes unless you clear some. Give production keys only what they use: a backend that values listings needs `valuations:write` and probably `catalogue:read`; a reporting job that reads results back needs only `valuations:read`.

## Create a key

Open **API keys** in the console. Owners, admins and developers can create keys. The options are:

| Option | What it does |
| --- | --- |
| **Key name** | A label you will recognise in logs and usage, such as `pricing-service-prod`. Required, up to 160 characters. |
| **Mode** | **Test · simulated, free** or **Live · uses credits**. It cannot be changed later; create a new key instead. |
| **Project** (optional) | A free-text label to group keys by project or environment. |
| **Expires at** (optional, UTC) | After this time the key returns `401 key_revoked`. Use it for temporary access, such as a proof of concept. |
| **Permissions** | The [scopes](#scopes) above. |
| **Monthly credit limit** (optional) | The most credits this key may use in a calendar month. When it is reached, live valuations with the key return `402 key_spend_limit_reached` until the month ends. It never grants extra credits. |
| **Allowed countries** | Restrict the key to some of the workspace's markets. Leave all unselected to inherit the workspace's markets. |

The full secret is shown **once**, right after you create the key. Copy it straight into your secret manager. The console afterwards shows only the key's name, prefix and last four characters.

## Countries: workspace and key

Two lists decide which markets a key may value:

1. **The workspace's markets.** New workspaces can value every publicly available market. AutoUncle can narrow this for your workspace.
2. **The key's allowed countries**, if you set any.

A market must pass both. Otherwise the valuation is rejected with `403 country_not_enabled` before anything is recorded or charged. `GET /v1/account` shows what passes both for the calling key in `allowed_countries`, as the ISO codes you send in `country`. A key's own restriction is also visible on the API keys page.

## Revoke a key

Choose **Revoke** next to the key on the API keys page. Revocation is immediate: the next request with that key returns `401 key_revoked`. Revoked keys stay listed so their usage history remains readable. Developers can revoke the keys they created; owners and admins can revoke any key.

Rotate keys by creating the new key, deploying it, checking traffic in **Request logs**, then revoking the old key.

## Team roles

A workspace has members with one of four roles. Invite people under **Settings**.

| Role | Can do |
| --- | --- |
| **Owner** | Everything an admin can. The person who created the workspace. An owner cannot be demoted or removed from the console. |
| **Admin** | Manage members and invitations, change roles, edit workspace settings, see and revoke every key, see all requests, usage and billing, and everything a developer can. |
| **Developer** | Create and revoke their own keys, use the playground and catalogue browser, and see request logs and usage for their own keys and their own OAuth agent connections. |
| **Viewer** | Read the workspace overview, usage and billing. Viewers cannot create keys, use the playground or open request logs. |

A workspace always keeps at least one owner or admin: the console refuses a role change or removal that would leave nobody in charge.

### Invitations

- An owner or admin invites a colleague by email with the role admin, developer or viewer.
- The invitation link is valid for **seven days**.
- It can be accepted only by a signed-in user whose **verified** email matches the invited address. Otherwise it fails with `email_verification_required`.
- **Resend** issues a fresh link and invalidates the previous one. Each invitation can be resent up to three times an hour.
- An expired, used or revoked link shows `invalid_invitation`. Ask an owner or admin to send it again.

## Agents and OAuth

MCP clients can connect with an API key header or by signing in with OAuth. An OAuth token is bound to the Data MCP endpoint and to the workspace you choose during consent, and your membership and scopes are checked again on every tool call. OAuth valuations are **live** and use credits. [OAuth or API key](/docs/mcp/oauth-or-api-key) explains how to choose.

## Key hygiene

- **Keep keys on the server.** Never ship a key in a browser app, a mobile app, a public repository or a shared notebook.
- **Never put a key in an agent prompt** or in a file an agent may print. Configure it as an environment variable or through the client's secret store.
- **Use one key per service and environment**, with only the scopes and countries it needs and, for live keys, a monthly credit limit.
- **Never log the `Authorization` header** or the full key. Log the key ID (`key_…`) from `GET /v1/account`, or the request ID from each response, instead.
- **Rotate keys** when people leave the team or a key may have been exposed, and revoke the old key.
- **Set an expiry** on keys for trials, demos and contractors.
