# Refusals & missing mileage

A refusal means "this input cannot be valued as sent". It is recorded, free, and comes with machine-readable recovery steps. This guide covers the refusals you will meet, the explicit mileage opt-in and strict resolution.

## What a refusal looks like

A refused valuation returns the normal valuation envelope with `"status": "refused"`, an `error` object and the error's HTTP status (usually `422`):

```json
{
  "request_id": "val_ff744d44049c52dd6cb9d22b13e45636",
  "object": "valuation",
  "country": "DK",
  "mode": "test",
  "status": "refused",
  "error": {
    "code": "missing_mileage",
    "status": 422,
    "detail": "Supply the actual mileage in vehicle.mileage, or explicitly set options.allow_mileage_estimation to true to use a fixed 96,000 km baseline assumption.",
    "errors": [{ "pointer": "/vehicle/mileage", "message": "Actual mileage or explicit baseline permission is required." }],
    "fix": [
      "Supply vehicle.mileage with its value and unit.",
      "If a fixed baseline assumption is acceptable, set options.allow_mileage_estimation to true. Strict resolution still requires actual mileage."
    ],
    "retryable": false,
    "billable": false
  },
  "notes": [],
  "attributions": [],
  "usage": { "billable": false, "credits_used": 0, "idempotent_replay": false, "mode": "test" }
}
```

Three things to rely on:

- **It is free.** Any credit reserved for the request is released. `usage.credits_used` is `0`.
- **It is recorded.** It has a `request_id`, appears in the console request log, and replays if you send the same idempotency key and body again.
- **It tells you what to fix.** `error.errors[].pointer` points into your request, and `error.fix` lists the steps. Show `fix` to users or log it; match on `error.code`.

To try again after fixing the input, send the corrected body with a **new** idempotency key. Reusing the old key with a different body returns `409 idempotency_conflict`.

## Common refusals

| Code | HTTP | Typical cause | Fix |
| --- | --- | --- | --- |
| [`missing_mileage`](/docs/errors#missing_mileage) | 422 | No mileage, and no opt-in to the baseline | Send `vehicle.mileage`, or opt in (below) |
| [`vehicle_not_identified`](/docs/errors#vehicle_not_identified) | 422 | Make or model not recognized; or strict mode without a year | Send make and model, or catalogue IDs |
| [`catalogue_id_conflict`](/docs/errors#catalogue_id_conflict) | 422 | Catalogue IDs from different branches of the hierarchy | Send one consistent path, or only the lowest level |
| [`unknown_catalogue_id`](/docs/errors#unknown_catalogue_id) | 404 | An ID that does not exist (for example from an old cache) | Look the vehicle up again in the catalogue |
| [`vehicle_out_of_range`](/docs/errors#vehicle_out_of_range) | 422 | Year or mileage outside the supported range | Check the year and the mileage unit |
| [`unsupported_vehicle`](/docs/errors#unsupported_vehicle) | 422 | A characteristic the market's model does not cover, such as `flags.is_damaged` | Remove a flag only if it does not apply; otherwise the vehicle cannot be valued. See `error.reason` when present |
| [`precision_below_minimum`](/docs/errors#precision_below_minimum) | 422 | The result would be below your `options.min_precision` | Add the details in `fix`, or lower the minimum |

Refusals are never retryable unchanged: `retryable` is `false`. The [error reference](/docs/errors) lists every code with its recovery steps.

## Missing mileage

Mileage is one of the strongest price factors, so the service **does not assume it** unless you say so. Without `vehicle.mileage`, a request is refused with `missing_mileage`, without spending a credit. This also applies when you send only a description and the mileage cannot be read from it.

### Send the actual mileage

Always the best option. Send the value with its unit, and do not convert it yourself:

```json
{
  "mileage": { "value": 9000, "unit": "mil" }
}
```

`km` is kilometres, `mi` is miles and `mil` is the Scandinavian mile of 10 km. The service converts to the market's own unit for valuing; the result always reports the odometer in kilometres, as `vehicle.odometer.km`.

### Or opt in to the baseline

If you genuinely cannot get the mileage and an indicative figure is still useful, opt in explicitly:

**curl**

```bash
curl https://data-services.autouncle.com/v1/valuations \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: valuation-mileage-baseline-001' \
  -d @- <<'JSON'
{
  "country": "DK",
  "vehicle": {
    "make": "Volkswagen",
    "model": "Golf",
    "model_year": 2020
  },
  "options": {
    "allow_mileage_estimation": true
  }
}
JSON
```

**Ruby**

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

uri = URI("https://data-services.autouncle.com/v1/valuations")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = "valuation-mileage-baseline-001"
request.body = JSON.generate({
  "country" => "DK",
  "vehicle" => {
    "make" => "Volkswagen",
    "model" => "Golf",
    "model_year" => 2020
  },
  "options" => {
    "allow_mileage_estimation" => true
  }
})
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.post(
    "https://data-services.autouncle.com/v1/valuations",
    headers={
        "Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
        "Idempotency-Key": "valuation-mileage-baseline-001",
    },
    json={
        "country": "DK",
        "vehicle": {
            "make": "Volkswagen",
            "model": "Golf",
            "model_year": 2020
        },
        "options": {
            "allow_mileage_estimation": True
        }
    },
    timeout=30,
)
print(json.dumps(response.json(), indent=2))
```

**JavaScript**

```javascript
const response = await fetch("https://data-services.autouncle.com/v1/valuations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "valuation-mileage-baseline-001",
  },
  body: JSON.stringify({
    country: "DK",
    vehicle: {
      make: "Volkswagen",
      model: "Golf",
      model_year: 2020
    },
    options: {
      allow_mileage_estimation: true
    }
  }),
});
console.log(JSON.stringify(await response.json(), null, 2));
```

[Open this request in the console playground](https://data-services.autouncle.com/console/playground?example=valuation_mileage_baseline)

This uses a **fixed baseline of 96,000 km**, converted to the market's unit. It is an assumption, not an estimate of this car's mileage. The result:

- reports the baseline in `vehicle.vehicle.odometer.km`, with `source: "imputed"`, `basis: "assumed"` and `status: "assumed"` at `/vehicle/odometer` in `identification.fields`;
- carries the `km_imputed` warning and an `improve_precision` hint asking for the actual mileage;
- is capped at **`low` precision**.

```json
{
  "warnings": [
    { "code": "km_imputed", "message": "Mileage uses a fixed 96,000 km baseline, converted to the market's mileage unit. This is an assumption, not an estimate based on this vehicle. Supply the actual mileage before relying on this valuation." }
  ],
  "improve_precision": [
    { "field": "mileage", "reason": "imputed", "expected_impact": "high", "message": "Provide the actual mileage." }
  ]
}
```

Show these to users. Never present a baseline result as a valuation of the actual car. The default is `false`, and the console playground's form has the same opt-in as an unchecked box.

## Missing year

The year behaves differently from mileage. In the default resolution, a missing registration year **is assumed**: the result carries a `year_imputed` warning and an `improve_precision` hint with reason `imputed` for `model_year`. Send `model_year` or `first_registration` whenever you can.

## Strict resolution

`options.resolution: "strict"` is for workflows where an assumption is never acceptable, such as lending or insurance decisions:

```json
{
  "options": { "resolution": "strict" }
}
```

Strict mode:

- refuses a missing registration year or mileage, **even when `allow_mileage_estimation` is `true`**;
- never calls AI interpretation (like `no_ai`);
- otherwise values normally.

Combine it with catalogue IDs and `min_precision` to get either a well-founded result or a free refusal:

**curl**

```bash
curl https://data-services.autouncle.com/v1/valuations \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: valuation-strict-001' \
  -d @- <<'JSON'
{
  "country": "DK",
  "vehicle": {
    "catalogue": {
      "drivetrain_id": "dv-947a3199394a5a4b41cb2869"
    },
    "model_year": 2021,
    "mileage": {
      "value": 45000,
      "unit": "km"
    }
  },
  "options": {
    "resolution": "strict",
    "min_precision": "medium"
  }
}
JSON
```

**Ruby**

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

uri = URI("https://data-services.autouncle.com/v1/valuations")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = "valuation-strict-001"
request.body = JSON.generate({
  "country" => "DK",
  "vehicle" => {
    "catalogue" => {
      "drivetrain_id" => "dv-947a3199394a5a4b41cb2869"
    },
    "model_year" => 2021,
    "mileage" => {
      "value" => 45000,
      "unit" => "km"
    }
  },
  "options" => {
    "resolution" => "strict",
    "min_precision" => "medium"
  }
})
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.post(
    "https://data-services.autouncle.com/v1/valuations",
    headers={
        "Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
        "Idempotency-Key": "valuation-strict-001",
    },
    json={
        "country": "DK",
        "vehicle": {
            "catalogue": {
                "drivetrain_id": "dv-947a3199394a5a4b41cb2869"
            },
            "model_year": 2021,
            "mileage": {
                "value": 45000,
                "unit": "km"
            }
        },
        "options": {
            "resolution": "strict",
            "min_precision": "medium"
        }
    },
    timeout=30,
)
print(json.dumps(response.json(), indent=2))
```

**JavaScript**

```javascript
const response = await fetch("https://data-services.autouncle.com/v1/valuations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "valuation-strict-001",
  },
  body: JSON.stringify({
    country: "DK",
    vehicle: {
      catalogue: {
        drivetrain_id: "dv-947a3199394a5a4b41cb2869"
      },
      model_year: 2021,
      mileage: {
        value: 45000,
        unit: "km"
      }
    },
    options: {
      resolution: "strict",
      min_precision: "medium"
    }
  }),
});
console.log(JSON.stringify(await response.json(), null, 2));
```

[Open this request in the console playground](https://data-services.autouncle.com/console/playground?example=valuation_strict)

With a test key this particular example is refused with `precision_below_minimum`, because test results are always `indicative`. With a live key it returns a result of at least `medium` precision, or a refusal.

## Refused or failed?

| | `refused` | `failed` |
| --- | --- | --- |
| Cause | The input | A service (timeout, outage, invalid response) |
| HTTP | 4xx | 5xx |
| `retryable` | `false` | Usually `true` |
| What to do | Fix the input, new key | Back off, then retry with a new key |
| Charged | No | No |

See [errors & retries](/docs/errors) for failures and [retrieve, replay & reconcile](/docs/guides/polling-and-replay) for handling unknown outcomes.

## Next steps

- [Improve precision with hints](/docs/guides/improve-precision).
- [Errors & retries](/docs/errors).
- [Test with test keys](/docs/guides/testing) to exercise refusals on purpose.
