For coding agents
Want to connect your own AI app to Truvo to get quotes for yourself? Go to truvo.com/ai.
This file is for coding agents. It covers what the Truvo API does, how to authenticate, every operation, the sandbox, and the rules for errors, retries, polling, and request keys, and each section links to the guide with more detail. Every example uses the sandbox, where quotes are synthetic and a key never reads a live record or causes a live effect.
What Truvo does
The Truvo API gets insurance quotes from inside your product. You send one applicant’s facts for one line, Truvo gets the quotes in the background, and you read the result when it’s ready.
- Lines:
personal_auto,renters, andpet. One quote request covers one line. - Pet: the customer buys on the carrier’s site.
Terms
Keys and environments
Send your key as a bearer token on every request:
- Both environments use the base URL
https://api.truvo.com/v1/, and the key prefix picks the environment:trv_sandbox_issandbox, andtrv_live_islive. After the prefix, a key has 32 letters and digits. - Truvo reads the environment and the customer from the key. Don’t send
environment,customer, orcustomer_idin the query or the body, or anX-Environment,X-Customer, orX-Customer-Idheader, because the API refuses that with422invalid_input. - The sandbox works before your organization is approved. Live traffic needs approval. A server key can call
GET /v1/capabilitiesbefore it sends a quote request. A platform key reads capabilities with MCPget_capabilities. While approval is pending, the server key gets200with no capabilities andaccess.liveset tonot_approved. A platform key readsaccess.livethrough MCPget_capabilities. Other server-key requests to key-protected routes that are on return403live_access_not_approved, with the reasonpending_review, unless the address is rate limited. Other MCP tool calls return that answer as a tool error withisError: true. Public routes stay unchanged. To get live access, an owner or admin books a call from the Go live page of the portal. After the call, Truvo approves the organization and issues its live keys. - A key proves which integration is calling, and Truvo checks the integration’s authority over each customer separately. A resource that doesn’t exist and one outside your authority get the same
404. A server key whose live access is waiting for approval gets the shared403on a key-protected route that is on, unless the address is rate limited. - A missing or invalid key gets
401authentication_failed. An outage never returns401, so treat a401as a problem with the key. - Every answer, success or error, carries a
Request-Idheader. An error body repeats it asrequest_id. - Every request to an API operation also sends a
Truvo-Versionheader with the revision ID that your code is built for, and the answer names the same revision. See Versions.
More: Keys and environments.
Operations
Every path sits under https://api.truvo.com. The OpenAPI document for these operations is https://api.truvo.com/v1/openapi.json, and the API reference shows the same schemas.
Each operation has one name, which the request log and the MCP tools use.
The feedback, webhook, and event feed operations answer 404, the same as an unknown path, while Truvo has them turned off.
Get a quote
These four steps price one personal auto quote in the sandbox. Set your key first:
1. Check what your key can do
The answer gives the line, the environment, the states, a JSON Schema of the facts the line requires, and the action types a quote can offer. Build the application from that schema, and ask the person for each fact it needs. Don’t fill in a fact with a guess.
The answer also gives Truvo’s quote disclosure in quote_disclosure. Before you ask for quotes, show the person its text as written and ask whether they agree. See The quote disclosure.
agent_guidance lists the rules Truvo asks an agent to keep when it acts for the person, each with a type and its text: facts_only, disclosure, results, and, while Truvo accepts feedback from your key, feedback. The MCP server gives the same rules in its instructions. The version changes whenever the text of a rule changes.
2. Start a quote request
Send the facts for one line, with a new request key. Every value in this body is synthetic:
quote_disclosure_versionis theversionfromquote_disclosurein step 1, andquote_disclosure_acceptedistrueonce the person agrees. The quote disclosure has the rules.- For auto, driver 1 is the named insured, and the API takes the applicant’s date of birth from driver 1.
- Money values are integers in minor units, so
10000000is $100,000.
The API answers 202 with a quote request whose status is in_progress. Save its id.
In the sandbox, this body returns one quote.
3. Wait for the result
Send the version from the 202 answer in since_version:
The read answers when the request changes, and a request in progress already shows the quotes so far. While the status is in_progress, read it again with the new version. Polling has a loop that does this.
4. Read a quote
Once the status is completed, quotes gives the ID and the version of each quote. Read one:
Without version, you get the latest version. Add ?version= to read a specific one.
The quote disclosure
Before you ask for quotes, the person agrees to Truvo’s quote disclosure. Truvo records their consent, so you send no consent receipt.
- Read
quote_disclosurein the answer ofGET /v1/capabilities. It holds the disclosuretext, itsversion, itssha256, and theprivacy_notice_url. - Show the person the
textas written, and ask whether they agree. - Only if they agree, send the
versioninquote_disclosure_versionandtrueinquote_disclosure_accepted. If they don’t agree, don’t send the request.
Their yes covers the quote, contact by phone, text, and email, and for auto a credit check. So every request needs the person’s email and a US phone number, on every line.
A request that breaks a rule gets one of these answers:
The quote request
Switch on status. It has five values, and a new one comes only in a new revision.
status_detailssays why: areasonand amessagethat you can show the person. The reasons today arequeuedandcollecting(in_progress),quoted,partly_quoted,no_quotesandoffline_quotes_only(completed),carriers_unavailableandno_carrier_answered(incomplete), andquote_run_failedandquote_run_not_started(failed). New reasons can arrive in any revision, so showmessage, and give any switch onreasona default branch.failedis an error on Truvo’s side and always says what to do next.incompleteis a finished result where carriers gave no final answer. Neithercompletednorincompleteis an error.quotesgives the ID and the version of each quote. A request returns only quotes that the person can buy, one for each product a carrier prices.market_outcomesgives one outcome for each carrier, with the carrier’s ID and name incarrier.idandcarrier.name:quote,offline_quote,decline,missing_information,pending, orno_answer. Aquoteoutcome names itsquote_idandquote_version, the carrier’s first quote when it has several.offline_quotemeans the carrier offered a price that the person can’t buy online through Truvo.assumptionslists the defaults that Truvo set before it asked carriers. Each has apath, avalue, asource, and alabelthat says why in plain words.next_actionslists what you can do next. A personal auto request that completes with a quote can listcontinue_on_truvo: aurlto Truvo’s results page for the request, to open in a new tab, where the customer can see more options and go on to buy. A live request lists it only when the customer agreed to the quote disclosure that includes contact consent. The page shows the customer’s details to anyone who opens the link, so give it only to the customer. In the sandbox, the link opens a Truvo sandbox page.status_details.retry_actionisnullunless the request isincompleteorfailed. When a request failed, follow its retry action. When it is incomplete, follow it only if you try a new request. In either case, use a newIdempotency-Key. A retry may still return no quotes.
A request with zero quotes completes with offline_quotes_only when a carrier reads offline_quote, or with no_quotes when every carrier result is a final answer. With no quote or offline quote, a decline beside a carrier that gave no usable answer ends as incomplete. You can try a new request later, but it may still return no quotes. When a request failed, follow its retry action. When it is incomplete, follow it only if you try a new request.
Truvo currently stops collecting answers 60 seconds after it accepts a quote request. A carrier that hasn’t answered by then reads no_answer. A finished request doesn’t change.
More: Get quote results.
The quote
The next action types are read_status, continue_on_carrier_site, prepare_purchase, open_hosted_checkout, and request_assistance. A quote lists only the ones it supports. New types can arrive in any revision, so skip an action of a type that you don’t know. Two of them work like this:
read_statusnames a resource to read again.continue_on_carrier_sitegives aurlto open in a new tab. The link goes through a Truvo redirect to the carrier’s site and works untilavailability.expires_at. After that, it opens a Truvo page that says the quote has expired. In the sandbox, the link opens a Truvo sandbox page, never a carrier site.
An inactive quote lists only read_status. Send a new quote request for a new price.
Mobile apps and web views
In a mobile app, open a continue_on_carrier_site link in the system browser or an in-app browser tab: SFSafariViewController on iOS, or Custom Tabs on Android. Don’t load it in an embedded web view such as WKWebView or Android WebView.
The link redirects to the carrier’s own site, where the customer enters personal details and pays. A browser tab shows the carrier’s address, and it gives the customer the browser’s saved passwords, autofill, and payment methods. An embedded web view shows no address, doesn’t share the browser’s saved data, and lets the host app read the page, which is the carrier’s checkout.
Rules for showing quotes to a person
- Offer only the actions a quote lists. Never infer an action from the line or the carrier.
- A quote is a price offer, not coverage. A checkout link isn’t proof of payment or coverage either.
- One carrier’s quote isn’t a market comparison.
- An unknown value stays unknown. Don’t fill in a price, a fee, or a limit that the quote doesn’t give.
- To give a monthly figure, divide
premium.amount_minorbyterm_monthsand round up to the dollar. Say that it’s the premium spread over the term, not a payment plan. A sandbox quote with apremium.amount_minorof 186000 and aterm_monthsof 6 is 310 a month spread over the term. Whenterm_monthsisnull, show only the premium. - A sandbox quote has
evidence.labelmockand the coverage conditionSandbox premium. Not a carrier price.
Polling
A quote request runs in the background, and its quotes arrive one at a time. Each change raises the request’s version: the start of its run, each new quote, and the final status. A read never starts a new rating run.
- Waiting reads are the loop we recommend. Add
Prefer: wait=Nto hold the read open for up toNseconds, whereNis a whole number from 0 to 20, andsince_versionwith theversionfrom your last answer, starting with the202answer. - The read answers as soon as the request has a version newer than
since_versionor a status other thanin_progress, so a quote that arrives between two reads answers at once. Asince_versionhigher than the current version counts as the current one. Withoutsince_version, the wait counts from the version that the read finds first. - Without
Prefer: wait, a read answers at once. For quick polling, read every few seconds until the status is no longerin_progress. - When the server held the read open, the answer carries
Preference-Applied: wait=N. - When the server can’t hold the read, it answers at once without
Preference-Appliedand withRetry-After. Wait that many seconds before you read again. - A
Preferorsince_versionvalue that isn’t valid gets422invalid_input. - Webhooks push each change to your server instead.
- Over MCP,
get_quote_requesttakeswait_secondsin place of the header, and waits 20 seconds when you leave it out. Readstatusin each result. While it isin_progress, callget_quote_requestagain with the sameid,wait_seconds: 20, andsince_versionset to theversionyou just read. If you read by handle, send the sameread_handletoo.
This loop starts from the version in the 202 answer, waits out Retry-After when an answer has it, and stops when the status is no longer in_progress:
More: Get quote results, which compares webhooks, quick polling, waiting reads, and MCP on one example request.
Request keys
If a response is lost after you send a create, you can’t tell whether Truvo accepted it. Send the same request again with the same request key. If Truvo accepted the first one, it returns that request instead of starting a second.
POST /v1/quote-requests,POST /v1/event-subscriptions,POST /v1/event-subscriptions/{id}/rotate,POST /v1/event-subscriptions/{id}/test, andPOST /v1/event-subscriptions/{id}/replayrequire theIdempotency-Keyheader. Reads don’t take one.- A key is 1 to 255 characters long, made of letters, digits,
.,_,:, and-. Use a new key, such as a UUID, for each new request. - The same key with the same body starts no second request, and Truvo checks your current authority first. A quote request comes back as it is now, with the same
idand its currentstatus,version, andquotes, so the answer can differ from the first one. - The same key with a different body gets
409request_key_reused, with the retry actionuse_new_request_key. - A key’s scope is one environment, one integration, one resource, and one operation.
- Over MCP,
request_keycarries the same key, with the same replay rules. With a platform key or without a key, only a random UUID replays; see Retry safely with request keys.
A key replays for at least 24 hours.
More: Retry safely with request keys.
Errors
Branch your code on error.code and error.retry_action.type. The message is written for a person.
field_errors is a list of fields, each with a path, message and code, or null. A field may also include format, allowed_values or did_you_mean. doc_url has the form https://docs.truvo.com/v1/errors/<code>, and it opens the code table in the Errors guide.
quote_expired, price_changed, and purchase_not_supported belong to purchase operations, which the API doesn’t serve yet, so no answer returns them today.
When to retry
- A timeout or a network error. Send the same body with the same request key.
503service_unavailable. Wait for the time inRetry-After, then send the same body with the same key.500internal_error. Send the same body with the same key.429rate_limited. Wait for the time inRetry-After, then send the request again.409request_key_reused. Your body is different from the first request with this key. To make a new request, use a new key.- A quote request that ends
failed. Send it again with a new key.
Over HTTP, most answers carry the RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers. They describe the limit you’re closest to reaching. RateLimit-Reset is the seconds until its count resets. Most windows are fixed at one minute, and a burst of up to twice the limit across a window edge is possible. An answer can come without these headers, and GET /v1/capabilities always does, because it counts toward no limit. An MCP tool result has no rate limit headers, and when a limit stops a tool call, its text says when to try again. Over MCP, get_capabilities counts as one read.
The sandbox also has daily limits, which reset at 00:00 UTC. Each organization can send 5,000 quote requests a day, and each call that counts toward its quote request limit counts toward this one too. A webhook subscription change counts even when it fails validation. A new quote request counts only once its input passes validation and has every required fact. Until then, it counts as a read. When all organizations together have 25,000 sandbox quote requests accepted in a day, the sandbox is busy. Until 00:00 UTC, it then accepts new quote requests only from organizations with fewer than 500 accepted that day, up to 50,000 in all. A request that fails validation doesn’t count toward the 25,000 and 50,000 totals. A 429 from a daily limit says Try again after 00:00 UTC., and Retry-After gives the seconds until then.
Truvo can suspend an organization’s sandbox, for example when its traffic looks like abuse. Then each of its sandbox keys gets 403 organization_suspended with the retry action contact_support, and it can’t create sandbox keys, until Truvo restores it. Its live keys keep working.
Truvo can also suspend an organization’s live access. Then each of its live keys gets 403 organization_suspended with the retry action contact_support until Truvo restores it. Its sandbox keys keep working. A 401 authentication_failed still means the key itself is missing, revoked, expired, or not valid.
A 403 live_access_not_approved has the reason pending_review. A suspension uses organization_suspended and has no public reason. In both cases, follow the contact_support retry action.
More: Errors.
Versions
The path stays /v1, and the contract has revisions. Until the REST API opens to outside developers, Truvo serves one revision, with the pre-release ID 2026-09-26. When it opens, the first public revision gets its own ID, and Truvo stops serving 2026-09-26. The notice period and the 6-month promise start with the first public revision, so the pre-release ID is replaced with no alias.
- A revision ID is a date in the form
YYYY-MM-DD. The ID format may change before the first public revision. Revisions are in the order that they ship. - Send
Truvo-Versionon every request to an API operation, with the revision ID that your code is built for, exactly as the changelog lists it. The answer names the same revision, and its fields follow it./v1/mcpand the discovery documents (/v1/openapi.json,/v1/openapi/<revision>.json,/v1/revisions,/llms.txtand/.well-known/api-catalog) don’t need the header. GET /v1/revisionsneeds no key. It lists thecurrentrevision and every revision with itsstatus(current,supportedorretired), newest first./v1/openapi/<revision>.jsonis the OpenAPI document of one served revision.- A missing header, or one that names no revision that Truvo serves, gets
400invalid_version. Its message says which revision to send, andfield_errors[0].allowed_valueslists each revision that Truvo serves, newest first. - Truvo stores no revision for your integration. The revision lives in your code, so an upgrade or a rollback is a code change. Each request names its own revision, so you can move one request at a time.
/v1/mcpalways answers in the current revision and ignores the header. Code that needs a stable contract calls the REST API. After a breaking change to a tool’s input, the tool keeps accepting the old input for the 6-month notice period, then answers with a field error that names the new field.- Webhook events have no revision, and an event’s fields are the same in every revision. Read the resource over REST with your own
Truvo-Version.GET /v1/eventsis an API operation, so it sends the header too. - A breaking change ships only in a new revision, and a revision keeps working for at least 6 months after notice. The changelog and an email announce each new revision and the end date of the revision before it, and before a revision retires, its answers carry
DeprecationandSunsetheaders. After the end date, a request that names the revision gets400invalid_version. A new status value is a breaking change. - Any field in an answer can get new values in any revision, except each resource’s
statusandretry_action.type. New fields can arrive too. Ignore a field you don’t know, and give each switch on a value a default branch. - A
404uses the current revision. So does a refusal that Truvo sends before it knows your revision:401,413, a429for failed authentication, or400invalid_version. Their top-level error fields stay compatible from one revision to the next. - “Revision” is a version of the API contract. “Version”, as in
resource.versionor a quote’sversion, is a step in the history of one object.
More: Versions and changes.
Webhooks
Instead of polling, subscribe a URL to the event types you want, and Truvo sends a signed POST each time one happens. The Webhooks guide has the details and the list of event types.
- Subscribe. Send
POST /v1/event-subscriptionswith anIdempotency-Key, anhttpsurlon a public host, and the exactevent_typesyou want. There’s no wildcard. The answer shows thesigning_secretonce, so store it right away. Only a server key can manage subscriptions, and each integration can hold 10 active subscriptions in each environment.PATCHandDELETEon/v1/event-subscriptions/{id}change or disable one, andPOST /v1/event-subscriptions/{id}/rotatereplaces the secret. The old secret keeps working for 24 hours. - Verify. Each delivery carries the
webhook-id,webhook-timestamp, andwebhook-signatureheaders. The signature is an HMAC-SHA256 over{webhook-id}.{webhook-timestamp}.{body}, keyed with the part of your signing secret afterwhsec_, decoded from base64. Verify the raw body, before any JSON parsing. The signature follows the Standard Webhooks scheme, so any Standard Webhooks library can verify it. Thestandardwebhookspackage on npm also refuses a timestamp more than 5 minutes from your clock. - Retries. Answer
2xxwithin 15 seconds. Any other answer counts as a failure, and the message is sent again after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours. If every delivery fails for 5 days, delivery turns off until you sendPATCHwith your URL. The same event can arrive more than once and out of order, so skip anidyou’ve already processed. - Thin events. An event names the resource that changed, in
resource.type,resource.id, andresource.version, and it carries no customer data. Read the resource to see what changed, and never apply an olderresource.versionover a newer one. Ignore an event type or a field you don’t know, and still answer2xx. - Resync through the feed.
GET /v1/eventsholds every event of the last 30 days, with the sameidas its webhook. Each page hasevents,has_more, andnext_cursor. Page through it withcursorandlimit(10 by default, 100 at most), and store eachnext_cursoronly after your work is saved. Whilehas_moreistrue, sendnext_cursorascursorto read the next page. Whenhas_moreisfalse, you’ve caught up; keepnext_cursorand read again later. Afterevent_cursor_expired, read each resource you track, then read the feed again without a cursor.
To check your endpoint, send a test event with POST /v1/event-subscriptions/{id}/test and a new Idempotency-Key. To see a real delivery in the sandbox, start a quote request.
MCP
The Truvo MCP server at https://api.truvo.com/v1/mcp serves the quote operations as tools, over Streamable HTTP, with the same key as a bearer token.
The server lists the five webhook tools based on the caller’s live-access and quoting status. A refused call comes back as a tool error that carries the same error envelope as HTTP. The server always answers in the current revision, and it ignores a Truvo-Version that the request sends.
More: MCP tools, with a page for each tool’s inputs, result, and callers, and Agent setup, with the config for Claude Code, Cursor, Codex, VS Code, OpenClaw, and Hermes Agent.
Feedback
If the API is missing something, an answer is wrong, or an error or a page is unclear, tell Truvo.
An agent that acts for a person asks before it sends a report, shows the person the report, and sends it only if they agree. It offers after a quote request finishes, when the person seems frustrated, or when the API gives it trouble it can’t fix, and at most once per conversation unless the person asks. The MCP server instructions say the same. For a report on how a quote went, use the kind other, and describe what happened in your own words rather than pasting the conversation.
POST /v1/feedback takes a JSON body:
kind:missing_feature,bug,unclear_error,unclear_docs, orother. Required.summary: one line, up to 200 characters, that says what went wrong or what’s missing. Required.expectedandactual: what you expected, and what happened instead, up to 2,000 characters each. Required.tried: theoperationor MCP tool you called, and therequest_idsof up to 20 calls that show the problem.agent: your agent’sname, such as Claude Code, and itsversion.
The API answers 201 with an id that starts with fb_ and the status received. GET /v1/feedback/{id} returns the report’s status (received, planned, shipped, or declined) and an optional note from Truvo. Leave out personal data. The API refuses text that looks like a Social Security, driver’s license, or card number.
Links
- Quickstart: the same first quote, step by step.
- Agent setup: connect an MCP client, and an
AGENTS.mdblock for your repo. - Webhooks: subscriptions, signatures, retries, event types, and the event feed.
- Versions and changes: revisions, the
Truvo-Versionheader, and what counts as a breaking change. The changelog lists each revision. - API reference: every operation and its schemas.
- MCP tools: each MCP tool, with a page for its inputs, result, and callers.
/llms.txt: an index of every docs page. Add.mdto any page URL to get its Markdown.
Common questions
- How long until quotes finish?: auto times measured on truvo.com, and the current 60-second deadline.
- Why did a request finish with no quotes?: each carrier outcome, and what to do about it.
- Why are there fewer quotes than carriers asked?: partial results and the deadline.
- When does a quote expire, and what happens then?:
expires_at, thequote.expiredevent, and what to show. - Request a quote for auto, renters, or pet: the disclosure step, a minimal request, each request field, and the
needs_informationerror. - Which states and carriers are covered?: states in capabilities, and carriers in each quote request.