AutoUncle Data Services is in beta. The API, MCP tools and agent skill may change often, including breaking changes, until version 1.0. Read the changelog before you upgrade an integration.
Test with test keys
Test keys let you build and test your whole integration for free: requests, refusals, retries, limits and result handling. This page explains exactly what test mode simulates, what it does not, and how to write tests you can rely on.
What a test key is
A test key starts with au_test_ and is created under API keys with Mode: Test · simulated, free. It uses the same endpoints, scopes, validation, catalogue and error contract as a live key. The difference is the valuation itself:
| Test key | Live key | |
|---|---|---|
| Price | Simulated by a deterministic formula | Real market valuation |
| Credits | Never | One per valued result |
precision.grade |
Always indicative |
high, medium, low or indicative |
model.model_id / model.synthetic |
sandbox-v1 / true |
The production model / false |
valuation.intervals[].method |
synthetic |
The model's method |
| AI interpretation of text and photos | Never runs | Runs when enabled and needed |
| Notes | Includes synthetic_result |
No synthetic_result |
| Concurrency | Its own limit of 4 per workspace | 4 per workspace, plus shared market capacity |
| Fair-use allowance | Valuations, selling-time forecasts, normalization and VIN, HSN/TSN and plate lookups share 10 per minute, 100 per hour and 300 per day per workspace | None beyond the rate limits and credits |
| Idempotency keys and read-back | Separate from live | Separate from test |
| Request logs | Recorded with mode test |
Recorded with mode live |
Catalogue and account calls behave identically for both modes and are free either way.
Test keys are for testing
Test keys exist to build and test an integration. Normalization and the vehicle identification in a test valuation use the real catalogue, so a test key could look like a free way to identify real vehicles. It is not allowed: the service terms forbid using test keys to check, identify or value real vehicles for your business or your users, and AutoUncle monitors test-key usage per workspace and key.
To keep that fair, test-key requests that identify a vehicle share a fair-use allowance per workspace: 10 per minute, 100 per hour and 300 per day. Catalogue and account calls do not count. When a window is used up, the request is refused with 429 test_limit_reached and retry_after, before anything is recorded; a refused request does not count against the other windows. Keep test suites small, stub the API in unit tests, and contact us if your CI needs a higher allowance.
What the simulated price depends on
The test price is a deterministic function of the registration year, mileage, power and market. It does not depend on make, model or equipment, so a test Golf and a test Porsche with the same year, mileage and power get the same price. The intervals are fixed proportions of the price (±20% for 80%, ±35% for 95%, ±50% for 99%).
This is deliberate: test results let you check your parsing, display and error handling, never market logic. Never show a test price to users as a market value. Detect test results with mode: "test", model.synthetic: true or the synthetic_result note.
What you can test
| Behaviour | How to trigger it with a test key |
|---|---|
| A valued result | A structured request with year and mileage |
| Ambiguity warnings and hints | Send make and model without catalogue IDs: generation_ambiguous and drivetrain_unresolved |
| A pinned vehicle | Send a catalogue drivetrain_id |
missing_mileage refusal |
Leave out vehicle.mileage |
| Mileage baseline | Leave out mileage and set options.allow_mileage_estimation: true |
| Assumed equipment | Set options.allow_equipment_estimation: true. Test keys assume only a catalogue trim's equipment, never similar vehicles' (equipment_not_assumed otherwise) |
| Assumed year | Leave out the year (best-effort resolution): year_imputed |
precision_below_minimum refusal |
Set options.min_precision to low or higher |
| Strict refusals | options.resolution: "strict" without a year or mileage |
catalogue_id_conflict |
Send a generation_id and a drivetrain_id from a different generation |
unknown_catalogue_id |
Send a catalogue ID that does not exist |
unsupported_vehicle |
Set vehicle.flags.is_damaged: true |
invalid_request with pointers |
Send an unknown field, or a string where a number is expected |
| Idempotent replay | Send the same body twice with the same key |
idempotency_conflict |
Send a different body with a used key |
images_not_used |
Add images; test mode never reads them |
country_not_enabled |
Value in a market your workspace or key does not allow |
| Rate limiting | Exceed 60 valuation requests in a minute (this also slows your other test traffic) |
What you cannot test
- Real prices, grades and intervals. Only a live key can show how precise results are for your vehicles.
- Free-text and photo interpretation. Test mode only runs deterministic text rules, so a description on its own is usually refused (
vehicle_not_identifiedormissing_mileage). Use structured attributes in tests, and judge text interpretation with a live key. - Dependency failures such as
engine_timeoutorresolution_unavailable. Simulate them in your own code with a stub that returns the documented error bodies from the error reference. - Credit exhaustion. Test keys never spend credits, so
insufficient_creditscannot happen. Stub it. engine_busy. Test valuations never use live capacity.
Writing reliable tests
- Assert on structure and codes, not on prices. Check
status,error.code,precision.grade, the presence ofvaluation.intervalsand your handling ofwarnings. Test prices change from one year to the next because they depend on the vehicle's age. - Use unique idempotency keys per test run, for example including a run ID. A key reused across runs replays the first run's result, which is useful for replay tests and confusing everywhere else.
- Keep CI under the limits. Run valuation tests with at most four in parallel and well under 60 per minute, or you will see
concurrency_limitedandrate_limited(both free and safe to retry afterretry_after). - Stub the API in unit tests with recorded responses, and keep a small set of contract tests that call the real API with a test key.
- Keep test keys out of production and live keys out of CI. Name keys by environment so the console's usage and logs stay readable.
A minimal contract test
import os
import uuid
import requests
API = "https://data-services.autouncle.com"
HEADERS = {"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}"}
def value(body, key):
return requests.post(f"{API}/v1/valuations", json=body, headers={**HEADERS, "Idempotency-Key": key}, timeout=30)
def test_valuation_contract():
run = uuid.uuid4().hex
body = {"country": "DK", "vehicle": {"make": "VW", "model": "Golf", "model_year": 2020,
"mileage": {"value": 60000, "unit": "km"}}}
first = value(body, f"ci-{run}-golf")
assert first.status_code == 200
result = first.json()
assert result["status"] == "valued"
assert result["mode"] == "test"
assert result["precision"]["grade"] == "indicative"
assert any(note["code"] == "synthetic_result" for note in result["notes"])
replay = value(body, f"ci-{run}-golf").json()
assert replay["request_id"] == result["request_id"]
assert replay["usage"]["idempotent_replay"] is True
refused = value({"country": "DK", "vehicle": {"make": "VW", "model": "Golf", "model_year": 2020}}, f"ci-{run}-no-km")
assert refused.status_code == 422
assert refused.json()["error"]["code"] == "missing_mileage"
Run it with pytest and a test key in AUTOUNCLE_API_KEY.
The playground
The console playground preselects a test key, so you can try any request by hand before writing code. Live keys are labelled consumes credits on success in the key picker.
Next steps
- Refusals & missing mileage.
- Production checklist before switching to a live key.
- Credits & billing.