# Workflows

Decision flows and recovery playbooks for integrations and agents. Background: https://data-services.autouncle.com/docs

## Before writing integration code

1. Confirm the user has a workspace and a **test** key in `AUTOUNCLE_API_KEY` (console: https://data-services.autouncle.com/console → API keys).
2. `GET /v1/account` → expect `200`, `key.mode: "test"`, the scopes you need.
3. Build the request mapping (below), the result handling and the error handling against the test key.
4. Only then switch to a live key, stored in the secrets manager, with narrow scopes and a monthly credit limit.

## Mapping a listing or stock record

| Source data | Request field |
| --- | --- |
| Market of the listing | `country` (ISO code) |
| Make, model | `vehicle.make`, `vehicle.model` (or catalogue IDs) |
| Engine/trim text, such as "1.5 TSI Life" | `vehicle.variant` |
| First registration date | `vehicle.first_registration` (YYYY-MM-DD) or `vehicle.model_year` |
| Odometer | `vehicle.mileage` `{value, unit}` in the unit you have |
| Fuel, gearbox, driven wheels | `vehicle.fuel`, `vehicle.transmission`, `vehicle.drive` (enum values) |
| Power in kW | Convert to hp (× 1.35962) for `vehicle.power_hp` |
| Engine size in cc | convert to litres for `vehicle.engine_size_l` |
| Body, doors, seats, colour | `vehicle.body`, `vehicle.doors`, `vehicle.seats`, `vehicle.color` |
| Equipment list | `vehicle.equipment` with keys from `/v1/catalogue/enums` |
| Damage, leasing price, no tax | `vehicle.flags` `{is_damaged, is_leasing_price, has_no_tax}`; only the ten documented flags count, others give a `flags_not_recognized` warning |
| Dealer or private listing | `pricing.price_basis` `dealer_retail` / `private_sale` |
| Asking price | `pricing.asking_price` (market currency) |
| Free text | `description` (strip personal data) |
| Photos | `images` `[{url}]` (public HTTPS) or `[{data, media_type}]` |

Omit anything unknown. With a live key, `vin` and `registration_plate` are looked up (plates in DK only; nothing in DE, IT or ES) and only fill what you left out.

A `vehicle` returned by `POST /v1/vehicles/normalize`, `/decode`, `/hsn-tsn` or `/lookup` (or their MCP tools) is an AVS vehicle document and can be sent back unchanged as the request's `vehicle` (without `identification`). Replace `vehicle.odometer` with today's reading first (`{"km": …}`, without `readAt`): a register's reading is from the last inspection (`readAt`) and is ignored.

## Interpreting a result

```
status == "valued"   → use valuation + precision; check identification.status, warnings, improve_precision
status == "refused"  → input problem: read error.code, error.errors[].pointer, error.fix; change input; new key
status == "failed"   → service problem: free; retry later with a new key if error.retryable
```

- `precision.grade`: `high` > `medium` > `low` > `indicative`. Test results are always `indicative`.
- `identification.fields` (keyed by JSON Pointer) shows where each value came from. `status: assumed` means a default, not evidence; `conflict` means your value contradicted the catalogue and was replaced; `ambiguous` means competing records are in `identification.candidates`.
- `improve_precision[]` lists what to add next, ordered by usefulness.
- `comparables` (only with `options.include: ["comparables"]`): `included`, `none_found` (rare vehicle; the valuation still stands) or `unavailable` with `unavailable_reason` `market_restricted` (Denmark) or `temporarily_unavailable` (request again with a new key). Comparables never change the price; `synthetic: true` marks test-key examples.
- `trade_in_price` (only with `options.include: ["trade_in_price"]`): `included` with `price`, `low` and `high`, or `unavailable` with `unavailable_reason` `market_unsupported` (outside its twelve markets, e.g. Finland, Romania, Belgium) or `temporarily_unavailable` (request again with a new key). It is an estimate of a fair trade-in price, not an offer, and relates to the dealer retail value whatever the price basis.
- `similar_cars_url` (only with `options.include: ["similar_cars_link"]`): a link to similar cars for sale on the market's AutoUncle site, free in every market; null with a `similar_cars_link_unavailable` warning when it could not be built.

## Recovery playbooks

**Network error or timeout with no response.** Retry the identical request with the same idempotency key, with backoff (for example 1 s, 2 s, 4 s, then stop). You get the recorded outcome or the result of the first execution; you are never charged twice.

**429 `rate_limited`.** Per-minute limit reached. Wait `retry_after` (REST also sends `Retry-After`; `RateLimit-Remaining` and `RateLimit-Reset` headers show the budget). Retry with the same key.

**429 `concurrency_limited` / 503 `engine_busy`.** Too many valuations in flight (yours, or the market's). Not started, not charged. Wait `retry_after` (about 2 s), retry with the same key, and lower your parallelism.

**409 `request_in_progress`.** The first attempt with this key is still running. Wait and retry the same key and body.

**409 `idempotency_conflict`.** Bug: a key was reused for different input. Generate keys per intended valuation, for example from a stock ID plus a content hash.

**402 `insufficient_credits` / `key_spend_limit_reached`.** Stop the batch. Report how many were valued. Do not retry until an owner adds credits or raises the limit.

**422 refusals.** Fix input per `fix[]`. Typical: `missing_mileage` (ask for mileage), `vehicle_not_identified` (add make/model or IDs), `catalogue_id_conflict` (send one consistent path), `precision_below_minimum` (add hints or lower `min_precision`). Use a new key for the corrected request.

**5xx `failed` (timeouts, unavailable, invalid response).** Free and recorded. Back off and retry later with a new key. If interpretation of text or photos failed, sending structured attributes avoids that dependency.

**`engine_misconfigured`, `internal_error`.** Do not retry in a loop. Report the `request_id` to support.

## Batch pattern

- Keep at most 4 live valuations in flight per workspace (the default concurrency limit) and stay under 60 valuation requests per minute (default rate limit). Test keys have their own concurrency limit of 4.
- Derive the idempotency key from the item, such as `stock-4711-v3`, so a re-run of the batch replays finished items instead of charging again.
- Treat `rate_limited`, `concurrency_limited` and `engine_busy` as "wait and retry the same item".
- Stop the whole batch on `insufficient_credits`, `key_spend_limit_reached`, `invalid_api_key`, `key_revoked`, `insufficient_scope` or `organization_suspended`.
- Store `request_id`, `status`, price, currency, grade, notes and attributions per item. Read a stored result later with `GET /v1/valuations/{request_id}` (no `usage` block).
- Before a live batch, tell the user the number of items and the maximum credit cost, and get a yes.

## Reading results back

`GET /v1/valuations/{id}` returns the recorded result exactly as produced, including notes and attributions, but not `usage`. A test key only sees test results and a live key only live results; otherwise `404 not_found`. There are no webhooks: poll or replay by ID.
