# Decision guides

Each guide starts with what the caller actually has, then selects a documented goal and public operation. The OpenAPI contract remains authoritative for exact schemas and errors. If a branch below says “not documented”, stop rather than inventing an endpoint.

## I have a VIN

### Goal: retrieve vehicle data

1. Call `GET /vin/{vin}/vehicle` with the request parameters documented for the customer contract.
2. If the response requires a browser redirect, follow the documented redirect-session flow; otherwise consume the direct result.
3. Read `tapiId`, the returned selection value and the documented vehicle data from the success response.
4. Persist the VIN and `tapiId` separately; `tapiId` represents the matched technical vehicle type, not the individual physical car.
5. Continue with `GET /vehicles/{tapiId}` for technical vehicle data or `POST /vehicles/{tapiId}/listing` for listing text when needed.

Treat the numeric selection value as an opaque contract value. Do not infer capabilities, suppliers or data provenance from it.

### Goal: retrieve vehicle-related parts

1. Call `GET /vin/{vin}/parts` with a full VIN and the request parameters documented for the customer contract.
2. Keep the optional numeric selection consistent for the same VIN. A conflicting value returns `409 vin_provider_mismatch`.
3. On `200`, map `parts[]`, including each required `tapiGenArt` and `vdi` field, plus `matchLevel`, `provider` and the returned `tapiId` according to the schema. Preserve `vdi=[]` as the complete value for a position without a valid mapping.
4. On `202`, persist the job and poll the required full `statusUrl` with `GET /vin/parts/jobs/{jobId}` after `Retry-After`. Compare `Location` with that documented URL and quarantine a mismatch rather than resolving its current root-relative example against the API origin.
5. Treat `matchLevel` as part of the result, not optional display text.

Do not retry the order merely because it returned `202`. Do not discard positions. A successful VIN-parts position always includes exactly one `tapiGenArt` and one `vdi` array; an empty `vdi` array does not keep the job pending.

### Goal: check an OE-number cart against the VIN

Use `POST /vin/cart-check`, not a parts lookup or SEO response. On `202`, poll `GET /vin/cart-check/jobs/{jobId}`. Interpret the line results and the overall completeness field together. A negative line result in an incomplete comparison is not automatically a definitive exclusion.

### Goal: match vehicle recalls

Call `GET /recalls/vehicles/{vin}`. The capability is a series-level match against the documented official registers and carries data-version information. Do not turn a match into a statement that a particular physical vehicle is affected beyond what the response schema says.

### Goal: calculate an economic evaluation

Call `GET /vin/{vin}/economic-evaluation` only with a known full VIN and the documented request parameters. On `202`, poll the required `statusUrl` for `GET /vin/economic-evaluation/jobs/{jobId}`. Keep the assumptions and returned result character with the evaluation.

### Goal: calculate cluster-based VIN Economic Intelligence

Call `POST /vin/economic-intelligence` (`createVinEconomicIntelligence`) with a known full VIN, market country, currency, condition and a required `Idempotency-Key`. This follows the documented VIN-to-vehicle-parts-to-part-cluster Intelligence flow and is distinct from `GET /vin/{vin}/economic-evaluation`: its final result exposes `evaluationId`, pipeline coverage, a risk-adjusted portfolio, cluster candidates, confidence, provenance and warnings. The route belongs to a closed, allowlisted commercial pilot and currently returns `503` until VIN Economic Intelligence, approved positive pricing and atomic billing are enabled together. After enablement, a `202` is polled only through `GET /vin/economic-intelligence/jobs/{jobId}` (`getVinEconomicIntelligenceJob`); that status operation can continue to return `202` while work is pending. Do not automate acquisition from a result whose confidence, missing evidence or candidate exclusions violate the caller's review policy.

## I have an OE number

### Goal: validate and normalize the number

Call `GET /parts/oe/normalize` with the exact query parameter defined by OpenAPI. Use the returned `status` before reading `normalizedOeNumber`; the latter is `null` for every status other than `matched`.

Do not implement a local uppercase/whitespace rule and call it equivalent. Other OE operations state their own input treatment and may not use the same normalization behaviour.

### Goal: retrieve the public base part

Call `GET /parts/oe/{oeNumber}`. A `200` response is a closed public object containing exactly:

- `oeNumber`
- `normalizedOeNumber`
- `tapiGenArt`
- `vdi`
- `part`
- `fitment`
- `replacementChain`
- `references`
- `referenceNumbers`

No confirmed base hit returns `404`; an ambiguous input can return `422`. The required `part` object proves that a base record exists, but its public enrichments `manufacturer`, `name` and `listPrice` can each be `null`. Preserve `part.name` as the returned string or `null`. The response always includes `tapiGenArt` as a valid code or `null` and `vdi` as an array of valid full codes or `[]`; they are independent best-effort enrichments, and missing classification data does not invalidate the confirmed base part. `tapiGenArt`, `vdi`, fitment, a comparison number or a replacement edge cannot independently create a successful base-part hit.

Interpret the four relationship views separately. `fitment` contains the available type-level vehicle assignments and can be empty. `references[]` groups confirmed reference and comparison numbers by `manufacturer`, while `referenceNumbers.oe_oem_reference_numbers[]` is the consolidated confirmed list including the confirmed OE number. `replacementChain` carries confirmed directed `from`/`to` edges. Do not derive any of these views from another.

### Goal: retrieve aftermarket references

Call `GET /parts/oe/{oeNumber}/aftermarket-references`. This operation states that it uses the submitted string without OE normalization. Returned references are not installation guarantees. If the business workflow needs a confirmed base part, run the base OE lookup separately rather than treating a reference result as equivalent.

### Goal: evaluate price

Use `GET /parts/oe/{oeNumber}/price`. Preserve whether figures describe new or used ranges and the currency fields exactly as returned. The API result is an evaluation, not a sales-price instruction.

### Goal: resolve a part cluster and inspect market intelligence

Call `GET /parts/intelligence` (`resolvePartIntelligence`) with `oeNumber` and, when known, its manufacturer plus kType, NatCode or GenArt context. A required `Idempotency-Key` represents one business request even though this is a GET. The current closed, allowlisted commercial pilot does not deliver real Intelligence results until approved positive pricing and atomic billing are active. Approved Observed Supply data and the authorised dealer panel are not yet available in production, and adaptive refresh is disabled; without an eligible materialized snapshot, the route returns a documented error instead of starting a refresh. kType, NatCode and GenArt can disambiguate but cannot independently establish cluster identity. On `200`, retain the returned `clusterId`, `clusterVersion`, scope, timestamps, separated `observedSupply`, `verifiedSales` and `internalUsage` evidence, prices, standtime, trend, scores, confidence and provenance. After refresh enablement, a `202` is polled only through `GET /parts/intelligence/jobs/{jobId}` (`getPartIntelligenceJob`). Never map observed listings into verified sales or consume an Opportunity Score without its interval, factors, limitations and confidence.

### Goal: optimise an eBay or marketplace article listing

Call `GET /parts/oe/{oeNumber}/seo` with the target `marketplaceId`, the content `language` and `vehicleType`. This one operation answers every "optimise my eBay article", "which category and item specifics does this part belong in" and "write the listing title for this OE number" goal. There is no separate eBay endpoint: the documented `marketplaceId` values are the eBay marketplaces `EBAY_AT`, `EBAY_AU`, `EBAY_BE`, `EBAY_CA`, `EBAY_CH`, `EBAY_DE`, `EBAY_ES`, `EBAY_FR`, `EBAY_GB`, `EBAY_HK`, `EBAY_IE`, `EBAY_IT`, `EBAY_NL`, `EBAY_PL`, `EBAY_SG` and `EBAY_US`, with `EBAY_DE` as the default. `language` accepts `de`, `en`, `fr`, `es`, `it`, `nl`, `pl` and `zh`.

A successful response carries the listing-ready output for that marketplace and language:

- `content.ebayTitle` — the article title for the eBay listing headline, inside the documented 80-character limit.
- `categoryId` — the category identifier for the requested marketplace; for an `EBAY_*` marketplace, the eBay category. Required-present and `null` when none is returned.
- `itemSpecifics[]` — the article's item specifics as `{name, value[]}` entries, one name able to carry several values.
- `keywords[]` — search terms for the listing and shop search.
- `product` — `manufacturer`, `name` and `nameAddition` for product naming.
- `content.title`, `content.h1`, `content.metaTitle`, `content.metaDescription`, `content.slug`, `content.bulletPoints` — the shop-side SEO text with the documented length limits.

A no-match is the documented, uncharged HTTP `404` `PartSeoNoMatchResponse`, not a success variant. The operation also has operation-specific live-availability behaviour: `429 seo_live_requests_paused` and the optional `liveAvailability` object state when live enrichment can be requested again. Send no `Idempotency-Key`. Review the returned copy before publication; it is publication content and never a fitment claim.

### Goal: generate publication images

Call `POST /vision/part/generate`, then poll `GET /vision/part/generation-jobs/{jobId}` when accepted. This is image generation from an OE number, not evidence that a photographed stock item has the same appearance or condition. Preserve the documented synthetic/disclosure metadata.

### Goal: check part recalls

Call `POST /recalls/parts` with the request positions defined by OpenAPI. Preserve per-position output and register data versions. Do not substitute this for an OE base lookup or vehicle compatibility check.

### Goal: retrieve documented vehicle-type assignments

Call `GET /parts/oe/{oeNumber}` and read `fitment[]` for the documented reverse OE-to-vehicle-type assignments. The list contains the available assignments and can be empty.

Each result carries an opaque `vehicleTypeKey` plus its recorded `criteria[]`; it does not include make/model data and is not a VIN-specific installation guarantee. Criteria are unstructured display text without a documented AND/OR relationship, polarity or executable vocabulary. Preserve them with their key and do not turn them into local compatibility rules. An empty list means no assignment evidence could be returned under the rules above, not that the part fits no vehicle.

API 1.71.0 has no public operation that resolves `vehicleTypeKey` to make, model or `tapiId`, and the contract makes no worldwide completeness promise. If the question concerns one known vehicle, call `POST /vin/cart-check` with the VIN and OE-number position instead. Never substitute aftermarket references, VDI codes, `references`, `referenceNumbers` or replacement-chain edges for either evidence scope.

## I have a `clusterId`

### Goal: read the current scoped snapshot

Call `GET /parts/clusters/{clusterId}/intelligence` (`getPartClusterIntelligence`) with a required `Idempotency-Key` and explicit market dimensions. Supply `clusterVersion` when the workflow must pin the cluster composition. On `202`, poll `GET /parts/intelligence/jobs/{jobId}` (`getPartIntelligenceJob`); keep the returned scope and cluster version attached to the final result.

### Goal: compare historical snapshots

Call `GET /parts/clusters/{clusterId}/history` (`getPartClusterIntelligenceHistory`) with required `from`, `to`, market dimensions and `Idempotency-Key`. Preserve each point's `asOf`, `clusterVersion`, evidence, scores, confidence and `provenance.snapshotId`. This operation reads materialized history and has no `202` branch: it does not create missing snapshots or start a backfill job.

## I have a vehicle registration document

### Goal: extract normalized international document fields

For new integrations, call `POST /scanner/document/registration/international` with the exact `fileUrl` request property and omit `quality` or set it to `standard`. Personal-document extraction offers no other processing level. The response uses the normalized, country-aware field model. Use the [vehicle-registration field mapping](/mappings/vehicle-registration-field-mapping.md) when mapping printed country codes to canonical response fields.

Use `POST /scanner/document/registration` when an integration deliberately needs the national response envelope. The deprecated `/scanner/registration-document/v2` and `/scanner/registration-document` paths remain compatibility aliases for existing callers: they do not emit an HTTP redirect and each returns the documented response of its canonical counterpart.

### Goal: create a vehicle file in one workflow

Call `POST /vehicles/intake`. This operation combines registration-document analysis and vehicle matching and may return `tapiId` for later calls. Use it when the target workflow is vehicle intake, not when the caller only needs OCR fields.

### Goal: extract a calculation with its documented equipment

Use `POST /scanner/document/calculation` with the documented `fileUrl` and optional `quality=standard`; no enhanced or maximum level is offered for this personal-document operation. It returns the fixed `DocumentCalculationResponse`: the previous 11 calculation fields plus required `equipment[]`. The caller cannot submit an output schema or arbitrary field definition. Preserve `equipment[].codes[].value` as source strings, including leading zeroes, and keep `equipmentKind`, `sourceSection` and `sourcePage` with every row.

The deprecated `POST /scanner/document/extract` path is a compatibility path for strict clients of the previous contract. It returns the documented 11 top-level fields and omits `equipment`; it is not an HTTP redirect. Do not reuse one `Idempotency-Key` across the canonical and compatibility paths: their response projections differ and the API returns HTTP `409` instead of replaying a differently shaped result.

### Goal: extract rich vehicle and equipment data from a vehicle document

Use `POST /scanner/document/vehicle` with `fileUrl`, optional `quality=standard` and `includeSensitiveData=false` unless there is an authorised, purpose-bound need for named parties, contact, bank, payment or contract details. No enhanced or maximum level is offered for this personal-document operation. Choose this operation instead of calculation extraction when the goal is the rich `schemaVersion=1.0` vehicle contract: detailed identifiers, powertrain, transmission, energy, colours, `equipment[]`, warnings and the explicit sensitive-data boundary. Preserve equipment codes as strings exactly as returned, including leading zeroes. Keep `equipmentKind` (`standard`, `variant`, `special` or `null`) independent from `availability`; neither field may be derived from the other. The deprecated `/scanner/vehicle-document/extract` path is a compatibility alias with the same response contract, not a redirect.

Every response is marked `Cache-Control: no-store`. With sensitive output disabled, an `Idempotency-Key` may be used for identical retries. With `includeSensitiveData=true`, do not send that header: the API rejects it and never stores the sensitive response for replay.

### Next step after a detected VIN

Validate the response according to its schema, keep the document extraction record separate from the vehicle lookup result, then follow [I have a VIN](#i-have-a-vin). A detected string is input to a vehicle lookup, not proof that all document fields describe the resulting technical vehicle type.

## I have a parts label image

Choose the smallest operation that meets the goal:

| Goal | Operation | Result handling |
|---|---|---|
| Detect whether and where a part number is visible | `POST /scanner/label/basic` | Read only `hasPartNumber` and `where`; this response contains no recognized text. |
| Extract part/reference-number candidates | `POST /scanner/label/extract-partnumbers` | Confirm candidate numbers through the appropriate OE operation. |
| Extract all detectable label information | `POST /scanner/label/extract-all` | Support the documented production and sandbox response variants. |

Never persist an OCR candidate directly as a confirmed OE number. Keep the source image reference, candidate, confirmation result and operator decision distinct in the customer system.

## I have one or more images

Select by outcome, not by a generic “AI image” category.

| Desired outcome | Public operation | Boundary |
|---|---|---|
| Read a visible VIN | `POST /scanner/vin/extract` | A missing or ambiguous complete VIN can be returned as `null`; continue only after validation. |
| Describe visible damage | `POST /vision/damage/describe` | A description is evidence from submitted images, not a repair decision. |
| Grade visible part quality | `POST /vision/part/quality` | Store the grade with the supporting criteria exposed by the schema. |
| Remove a part-photo background | `POST /vision/part/remove/bg` | Use `found`, nullable `imageUrl`/media/dimensions, `coverage`, `sourcePixelsPreserved` and `limitations`; the response has no generic asset or provenance object. |
| Remove a vehicle-photo background | `POST /vision/vehicle/remove/bg` | Same cut-out response boundary; this is an image transformation, not vehicle identification. |
| Classify an end-of-life vehicle | `POST /vision/vehicle/elv-classification` | Keep evidence coverage and economic assessment separate from legal disposition. |
| Produce a vehicle condition report | `POST /vision/condition-report` | Use for walkaround-condition evidence, not registration-document extraction. |
| Read licence plates | `POST /vision/license-plate` | The contract explicitly does not perform keeper lookup, registration validation or authenticity proof. |
| Composite a part into a background | `POST /vision/part/composite` | Publication edit with fail-closed identity/redaction checks; not a photo of the actual stock scene. |
| Transfer visible damage to standard views | `POST /vision/part/damage-transfer` | Use only for the documented source/target image workflow and preserve synthetic provenance. |
| Redact part identifiers | `POST /vision/identifiers/redact` | Redaction is different from reading or validating an identifier. |

All scanner and Vision calls require a documented legal basis and rights for every submitted file and URL. Use only an explicitly approved storage and retention policy.

## I have a `tapiId`

- Use `GET /vehicles/{tapiId}` to retrieve documented technical vehicle data.
- Use `POST /vehicles/{tapiId}/listing` to compose marketplace-ready listing text from the documented vehicle data and request inputs.
- Do not send `tapiId` where an operation requires a VIN.
- Do not treat `tapiId` as the identifier of a physical stock vehicle; keep the integrating system's own vehicle-record ID.

## I need to check an OE cart against a VIN

Use this sequence:

```text
VIN selected for the Client
  -> POST /vin/cart-check with OE-number positions
  -> 200: map final response
  -> 202: store jobId + validated statusUrl + nextPollAt
           -> GET /vin/cart-check/jobs/{jobId}
           -> queued/running: wait according to Retry-After
           -> succeeded: map result
           -> failed: map embedded job error; do not treat HTTP 200 as success
```

Store input position IDs so results can be reconciled without relying on array order unless the schema explicitly guarantees order.

## I need to separate customers or applications

1. Keep the Master client API key in the partner backend.
2. For a complete partner onboarding action, use `POST /client/partner-workspaces` only when its combined allowlist/key/sponsorship contract matches the workflow.
3. Otherwise create a Sub-user with `POST /client/users`, then configure its rate limits with `PUT /client/users/{clientId}/rate-limits` and add keys with `POST /client/users/{clientId}/keys` as required.
4. Store each plaintext API key exactly once when issued; it cannot be retrieved later.
5. After an ambiguous network failure, enter operation-specific `reconciliation_required` state and never retry a key-issuing operation blindly. `POST /client/users` permits partial reconciliation through `GET /client/users`, but the lost raw key still cannot be recovered. The public contract provides neither raw-key recovery for `POST /client/users/{clientId}/keys` nor lookup by `externalReference` for `POST /client/partner-workspaces`.
6. Read usage through `GET /client/usage` or, as a Master client, `GET /client/users/{clientId}/usage`.

The integrating system's Tenant remains its own domain object. Map it to a Client; do not add an undocumented tenant field to API calls.

## I need a function that is not listed

Search the published OpenAPI contract by goal, tag, operation ID and path. If no matching operation exists under OpenAPI `paths`, the capability is not part of API 1.71.0. Do not fabricate a call.
