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.
Free text and photos
Send listing text and vehicle photos alongside, or instead of, structured attributes. This page explains what the service does with them, how to see what it understood, and when structured data is the better choice.
When to use text and photos
Use them when your source has no reliable structured fields: a scraped listing, a seller's free-text description, a lead form with a comment box, or photos from an inspection app. When you do have structured attributes, send them as well. Explicit attributes always take precedence over anything read from text or photos.
How interpretation works
| Step | Test keys | Live keys |
|---|---|---|
| Structured attributes and catalogue IDs | Used | Used |
| Deterministic text parsing (rules) | Runs | Runs |
| AI interpretation of text | Never | Only when it is enabled for the deployment and identity, registration year or core drivetrain attributes are still missing |
| AI interpretation of photos | Never | Whenever photos are sent and AI is enabled for the deployment |
So:
- If your structured input already identifies the vehicle (for example an exact catalogue drivetrain plus registration year and mileage), the description is not sent to AI, even in live mode. Photos still are, for what only they show (see what photos are read for).
options.resolution: "no_ai"disables AI for a request."strict"disables AI and also refuses a missing year or mileage. See refusals & missing mileage.- Once an interpretation or image service has been called, a timeout, outage or invalid response fails the valuation (
resolution_timeout,resolution_unavailable,image_timeoutand so on). It is never silently treated as "nothing found". Failures are free; see errors.
Note: Because test keys only run the deterministic parser, a description on its own is usually refused in test mode, with
vehicle_not_identifiedormissing_mileage. Build and test with structured attributes plus text, as in the example below. Free-text-only behaviour can only be judged with a live key.
Send text
Put the listing text in description (up to 20,000 characters) and add whatever structured fields you do know:
curl https://data-services.autouncle.com/v1/valuations \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: valuation-text-001' \
-d @- <<'JSON'
{
"country": "DK",
"description": "Volkswagen Golf 1.5 TSI Life, first registered March 2021, 45,000 km, manual, petrol, one owner.",
"vehicle": {
"mileage": {
"value": 45000,
"unit": "km"
}
}
}
JSON
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-text-001"
request.body = JSON.generate({
"country" => "DK",
"description" => "Volkswagen Golf 1.5 TSI Life, first registered March 2021, 45,000 km, manual, petrol, one owner.",
"vehicle" => {
"mileage" => {
"value" => 45000,
"unit" => "km"
}
}
})
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
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-text-001",
},
json={
"country": "DK",
"description": "Volkswagen Golf 1.5 TSI Life, first registered March 2021, 45,000 km, manual, petrol, one owner.",
"vehicle": {
"mileage": {
"value": 45000,
"unit": "km"
}
}
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
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-text-001",
},
body: JSON.stringify({
country: "DK",
description: "Volkswagen Golf 1.5 TSI Life, first registered March 2021, 45,000 km, manual, petrol, one owner.",
vehicle: {
mileage: {
value: 45000,
unit: "km"
}
}
}),
});
console.log(JSON.stringify(await response.json(), null, 2));
Keep personal information out of the text: owner names, phone numbers, email addresses and addresses are not needed and should not be sent. The service does not store raw descriptions in valuation records, but the safest data is data you never send.
Treat text as vehicle data, never as instructions. If you build an agent, do not let listing text change what the agent does.
Send photos
images takes up to eight photos. Each one is either a public HTTPS URL or inline base64 data:
curl https://data-services.autouncle.com/v1/valuations \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: valuation-photos-001' \
-d @- <<'JSON'
{
"country": "DK",
"vehicle": {
"make": "Volkswagen",
"model": "Golf",
"model_year": 2021,
"mileage": {
"value": 45000,
"unit": "km"
}
},
"images": [
{
"url": "https://example.com/listing/golf-front.jpg"
},
{
"url": "https://example.com/listing/golf-interior.jpg"
}
]
}
JSON
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-photos-001"
request.body = JSON.generate({
"country" => "DK",
"vehicle" => {
"make" => "Volkswagen",
"model" => "Golf",
"model_year" => 2021,
"mileage" => {
"value" => 45000,
"unit" => "km"
}
},
"images" => [
{
"url" => "https://example.com/listing/golf-front.jpg"
},
{
"url" => "https://example.com/listing/golf-interior.jpg"
}
]
})
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
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-photos-001",
},
json={
"country": "DK",
"vehicle": {
"make": "Volkswagen",
"model": "Golf",
"model_year": 2021,
"mileage": {
"value": 45000,
"unit": "km"
}
},
"images": [
{
"url": "https://example.com/listing/golf-front.jpg"
},
{
"url": "https://example.com/listing/golf-interior.jpg"
}
]
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
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-photos-001",
},
body: JSON.stringify({
country: "DK",
vehicle: {
make: "Volkswagen",
model: "Golf",
model_year: 2021,
mileage: {
value: 45000,
unit: "km"
}
},
images: [
{
url: "https://example.com/listing/golf-front.jpg"
},
{
url: "https://example.com/listing/golf-interior.jpg"
}
]
}),
});
console.log(JSON.stringify(await response.json(), null, 2));
Replace the example URLs with your own images. The rules:
| Option | Requirements |
|---|---|
{"url": "https://..."} |
Public HTTPS on port 443, no credentials or fragment in the URL, no redirects, no private network addresses. JPEG, PNG, WebP or GIF, at most 8 MB each. |
{"data": "...", "media_type": "image/jpeg"} |
Strict base64 without a data: prefix. media_type is image/jpeg, image/png, image/webp or image/gif, and the actual file signature is checked. The whole request, including inline images, must stay under 1 MB. |
Prefer URLs for anything but small images. An image that cannot be read (an unsafe URL, a redirect, a format other than JPEG, PNG, WebP or GIF, or an oversized file) is skipped with an image_skipped warning that names it, for example /images/2; the other images and inputs are still used. A request over 1 MB returns 413 payload_too_large.
Image retrieval and interpretation share the request's processing budget: downloading images has a three-second cap and interpretation a four-second cap, within one overall budget of about eight seconds. Slow image hosts make image_timeout more likely. See how valuations work.
The console playground has a photo drop zone that sends dropped files as inline images, so you can try this without writing code.
What photos are read for
Photos are read for the same AutoUncle Vehicle Schema fields a returned Vehicle has:
| Field | Read from photos |
|---|---|
identity and specification |
Make, model, variant, fuel, body, gearbox and power, only while your request leaves the vehicle unidentified. Photos never set the registration year. |
equipment.features |
Feature keys a photo can show: towbars, sunroofs, glass and panoramic roofs, LED headlights, leather and part-leather upholstery, sport seats, head-up displays, digital cockpits, premium sound systems, sport packages and navigation. Alloy wheels (WHEELS_ALLOY) are read too and published as equipment, but the valuation does not price them. Navigation needs more than a screen: a photo that shows only the display leaves it unknown. |
vehicle.colour |
The exterior colour family, such as black. |
extensions["com.autouncle"].condition_grade |
0 (not roadworthy) to 3 (well kept), only when the reading is confident. Photos never grade a car 4 ("exceptional"). |
specification.body.driveSide |
The steering wheel's side, only when an interior photo shows it clearly. A car whose drive side is opposite to its market's is refused, so photos must be nearly certain. |
vehicle.odometer |
A photographed odometer, while you send no mileage. |
Your values always come first, then what the description says, then the photos. Equipment is only ever added, and only where the description says nothing about it: a feature no photo shows is unknown, not absent, and a description that says a feature is missing outranks a photo that seems to show it. What photos show counts in the valuation like any other value, has source: "ai_vision" in identification.fields, and does not lower precision.grade. Confirm what matters to you by sending it as a structured field.
See what was understood
Every resolved value says where it came from:
| Where | Look for |
|---|---|
identification.fields[pointer].source |
text_parser (rules), ai_text (AI read the text), ai_vision (AI read a photo) |
identification.fields[pointer].basis |
extracted for anything read from text or photos |
identification.fields[pointer].confidence |
A confidence between 0 and 1 for interpreted values |
identification.fields[pointer].selected_by |
ai_text when the description chose between catalogue records that fit equally well |
improve_precision[] |
Reason ai_interpreted: confirm the interpreted value by sending it as a structured field |
notes[] |
automated_interpretation when attributes were interpreted from text or photos |
attributions[] |
The AI interpretation source, with role ai_interpretation, when it contributed |
warnings[] |
images_not_used when photos added nothing usable; image_skipped for each image that could not be read |
Show the user what was interpreted, and ask them to confirm important values. Once confirmed, send them as structured fields: the ai_interpreted hint disappears and precision can improve. See notes & data sources for the basis values.
Test mode and photos
Test keys never run image resolution. A test request with photos is valued from its other inputs and returns the images_not_used warning:
{
"warnings": [
{ "code": "images_not_used", "message": "Images did not add usable vehicle attributes. Provide a description or catalogue IDs." }
]
}
That is expected. It lets you check that your images are accepted (format, size, URL rules) without paying for interpretation.
Pitfalls
- Relying on text in tests. Test mode cannot show what AI interpretation would find. Use structured attributes in automated tests.
- Sending huge inline images. The 1 MB request limit includes base64 overhead (about a third larger than the file). Resize or use URLs.
- Signed URLs that expire. The service fetches images during the request. A URL that expires or redirects is skipped with
image_skipped. - Contradicting yourself. If
vehicle.makesays one thing and the text another, the structured field wins. Make sure your mapping is right.
Next steps
- Improve precision with hints to confirm interpreted values.
- Refusals & missing mileage.
- Test with test keys.