Skip to navigation

For coding agents

Everything an agent needs to use the Truvo API, in one Markdown file.
View as Markdown

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, and pet. One quote request covers one line.
  • Pet: the customer buys on the carrier’s site.

Terms

TermMeaning
quote requestThe asynchronous job that POST /v1/quote-requests starts. Its ID starts with qreq_.
quoteOne priced result, read with GET /v1/quotes/{id}. Its ID starts with qt_, and each quote has numbered versions.
linepersonal_auto, renters, or pet.
market_outcomesThe list on a quote request of the carriers that Truvo asked, and how each answered. Each entry names the carrier in carrier.id and carrier.name.
keyYour API key. It starts with trv_sandbox_ or trv_live_.
request keyThe value of the Idempotency-Key header. Over MCP, it’s the request_key argument.
final statuscompleted, incomplete or failed. A quote request in a final status doesn’t change again.

Keys and environments

Send your key as a bearer token on every request:

Authorization: Bearer trv_sandbox_...
  • Both environments use the base URL https://api.truvo.com/v1/, and the key prefix picks the environment: trv_sandbox_ is sandbox, and trv_live_ is live. 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, or customer_id in the query or the body, or an X-Environment, X-Customer, or X-Customer-Id header, because the API refuses that with 422 invalid_input.
  • The sandbox works before your organization is approved. Live traffic needs approval. A server key can call GET /v1/capabilities before it sends a quote request. A platform key reads capabilities with MCP get_capabilities. While approval is pending, the server key gets 200 with no capabilities and access.live set to not_approved. A platform key reads access.live through MCP get_capabilities. Other server-key requests to key-protected routes that are on return 403 live_access_not_approved, with the reason pending_review, unless the address is rate limited. Other MCP tool calls return that answer as a tool error with isError: 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 shared 403 on a key-protected route that is on, unless the address is rate limited.
  • A missing or invalid key gets 401 authentication_failed. An outage never returns 401, so treat a 401 as a problem with the key.
  • Every answer, success or error, carries a Request-Id header. An error body repeats it as request_id.
  • Every request to an API operation also sends a Truvo-Version header 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.

NameHTTPMCP tool
get_capabilitiesGET /v1/capabilitiesYes
start_quote_requestPOST /v1/quote-requests with an Idempotency-KeyYes
get_quote_requestGET /v1/quote-requests/{id}Yes
list_quote_requestsGET /v1/quote-requestsYes
get_quoteGET /v1/quotes/{id}Yes
send_feedbackPOST /v1/feedbackYes
get_feedbackGET /v1/feedback/{id}No
create_event_subscriptionPOST /v1/event-subscriptions with an Idempotency-KeyNo
list_event_subscriptionsGET /v1/event-subscriptionsNo
get_event_subscriptionGET /v1/event-subscriptions/{id}No
update_event_subscriptionPATCH /v1/event-subscriptions/{id}No
disable_event_subscriptionDELETE /v1/event-subscriptions/{id}No
rotate_event_subscription_secretPOST /v1/event-subscriptions/{id}/rotate with an Idempotency-KeyNo
send_test_eventPOST /v1/event-subscriptions/{id}/test with an Idempotency-KeyYes
list_event_deliveriesGET /v1/event-subscriptions/{id}/deliveriesYes
get_event_deliveryGET /v1/event-subscriptions/{id}/deliveries/{event_id}Yes
replay_event_deliveryPOST /v1/event-subscriptions/{id}/replay with an Idempotency-KeyYes
get_event_subscription_healthGET /v1/event-subscriptions/{id}/healthYes
recover_eventsGET /v1/eventsNo
list_api_requestsGET /v1/requestsYes
get_api_requestGET /v1/requests/{id}No

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:

export TRUVO_API_KEY="trv_sandbox_..."

1. Check what your key can do

curl https://api.truvo.com/v1/capabilities \
-H "Authorization: Bearer $TRUVO_API_KEY" \
-H "Truvo-Version: 2026-09-26"

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:

curl -X POST https://api.truvo.com/v1/quote-requests \
-H "Authorization: Bearer $TRUVO_API_KEY" \
-H "Truvo-Version: 2026-09-26" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"line": "personal_auto",
"quote_disclosure_version": "2026-10-06.1",
"quote_disclosure_accepted": true,
"application": {
"applicant": {
"name": { "first_name": "Test", "last_name": "Applicant One" },
"email": "alex@example.com",
"phone": "512-555-0123"
},
"garaging_address": {
"line1": "100 Example Way",
"line2": null,
"city": "Austin",
"state": "TX",
"postal_code": "78701"
},
"drivers": [
{
"name": { "first_name": "Test", "last_name": "Applicant One" },
"date_of_birth": "1990-04-12",
"gender": "female"
}
],
"vehicles": [
{
"vin": "SYNTHVIN000000001",
"year": 2019,
"make": "Toyota",
"model": "Camry",
"ownership": "owned",
"use": "pleasure",
"annual_mileage": 8000,
"comprehensive_deductible_minor": 100000,
"collision_deductible_minor": 100000
}
],
"protection": {
"bodily_injury_per_person_minor": 10000000,
"bodily_injury_per_accident_minor": 30000000,
"property_damage_minor": 5000000
}
}
}'
  • quote_disclosure_version is the version from quote_disclosure in step 1, and quote_disclosure_accepted is true once 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 10000000 is $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:

curl "https://api.truvo.com/v1/quote-requests/qreq_...?since_version=1" \
-H "Authorization: Bearer $TRUVO_API_KEY" \
-H "Truvo-Version: 2026-09-26" \
-H "Prefer: wait=20"

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:

curl https://api.truvo.com/v1/quotes/qt_... \
-H "Authorization: Bearer $TRUVO_API_KEY" \
-H "Truvo-Version: 2026-09-26"

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.

  1. Read quote_disclosure in the answer of GET /v1/capabilities. It holds the disclosure text, its version, its sha256, and the privacy_notice_url.
  2. Show the person the text as written, and ask whether they agree.
  3. Only if they agree, send the version in quote_disclosure_version and true in quote_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:

RequestAnswer
No quote_disclosure_version, or a version that isn’t the current one422 invalid_input, with the field error quote_disclosure_version: Send the version of Truvo’s current quote disclosure.
A quote_disclosure_accepted that isn’t true422 invalid_input, with the field error quote_disclosure_accepted: Send true only after the person agrees to the quote disclosure.
No email, or no phone number422 needs_information, with a field error for each missing fact.
A phone number that isn’t a US number422 invalid_input, with the field error application.applicant.phone: Send a US phone number with its area code, such as 512-555-0123. A live request gets the same message without the example. On pet, the path is application.owner.phone.
On a live request, a phone number that nobody could answer, such as 512-555-0123422 invalid_input, with the field error application.applicant.phone: This is not a phone number a person can answer. Send the person’s own number. On pet, the path is application.owner.phone. The sandbox accepts such a number.
consent_receipt_id, credit_disclosure_receipt_id, contact_consent, or contact_channels422 invalid_input, with a field error for each one: This field is not part of the request.

The quote request

Switch on status. It has five values, and a new one comes only in a new revision.

StatusFinal?Normal?What to do
in_progressNoYesRead again. Quotes can show before the request finishes.
action_requiredNoYesNot used yet. When used, show status_details.message, then follow next_actions.
completedYesYesShow the quotes. A completed request can hold no quotes.
incompleteYesYesShow status_details.message. Follow its retry action if you try a new request. A retry may still return no quotes.
failedYesNoShow status_details.message, then follow status_details.retry_action.
  • status_details says why: a reason and a message that you can show the person. The reasons today are queued and collecting (in_progress), quoted, partly_quoted, no_quotes and offline_quotes_only (completed), carriers_unavailable and no_carrier_answered (incomplete), and quote_run_failed and quote_run_not_started (failed). New reasons can arrive in any revision, so show message, and give any switch on reason a default branch.
  • failed is an error on Truvo’s side and always says what to do next. incomplete is a finished result where carriers gave no final answer. Neither completed nor incomplete is an error.
  • quotes gives 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_outcomes gives one outcome for each carrier, with the carrier’s ID and name in carrier.id and carrier.name: quote, offline_quote, decline, missing_information, pending, or no_answer. A quote outcome names its quote_id and quote_version, the carrier’s first quote when it has several. offline_quote means the carrier offered a price that the person can’t buy online through Truvo.
  • assumptions lists the defaults that Truvo set before it asked carriers. Each has a path, a value, a source, and a label that says why in plain words.
  • next_actions lists what you can do next. A personal auto request that completes with a quote can list continue_on_truvo: a url to 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_action is null unless the request is incomplete or failed. 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 new Idempotency-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

FieldWhat it holds
statusactive or inactive. Offer the quote’s next actions only while it’s active. A new value comes only in a new revision.
status_detailsWhy the quote has its status: a reason and a message that you can show the person. The reasons today are available (active), and expired (inactive). New reasons can arrive in any revision.
sourceThe carrier and the product that priced the quote, and carrier_quote_id: the carrier’s own number for the quote, or null. A person can give that number to the carrier. product is the name of the carrier’s product, or null when Truvo has none. A carrier can give a quote for each of several products.
pricepremium, with an amount_minor and a currency of USD, or null when the price is unknown. Also term_months, billing_cycle (pay_in_full or monthly), and fees. premium is the price for the whole term of term_months months, as the carrier states it, even when billing_cycle is monthly. term_months is null when the term is unknown. On a renters quote, premium already includes every fee in fees: show it as the total, and don’t add the fees to it. On other lines, the quote doesn’t say whether premium includes fees.
coverageconditions and the covered subjects.
availabilityexpires_at, when the quote turns inactive, and the value to rely on. Truvo currently sets it to the carrier’s expiry when the carrier gives one, and otherwise to 30 days after Truvo created the quote. Some renters quotes expire sooner, 24 hours after Truvo rated the quote or when the coverage start date begins at the rented address, whichever comes first. These defaults can change.
next_actionsThe actions this quote supports.
evidencelabel: mock for a made-up sandbox price, or live for a carrier’s price. Also the assumptions specific to this quote, such as a value the carrier changed. The quote request’s assumptions lists the ones every quote shares.

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_status names a resource to read again.
  • continue_on_carrier_site gives a url to open in a new tab. The link goes through a Truvo redirect to the carrier’s site and works until availability.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_minor by term_months and round up to the dollar. Say that it’s the premium spread over the term, not a payment plan. A sandbox quote with a premium.amount_minor of 186000 and a term_months of 6 is 1,860for6months,or1,860 for 6 months, or 310 a month spread over the term. When term_months is null, show only the premium.
  • A sandbox quote has evidence.label mock and the coverage condition Sandbox 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=N to hold the read open for up to N seconds, where N is a whole number from 0 to 20, and since_version with the version from your last answer, starting with the 202 answer.
  • The read answers as soon as the request has a version newer than since_version or a status other than in_progress, so a quote that arrives between two reads answers at once. A since_version higher than the current version counts as the current one. Without since_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 longer in_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-Applied and with Retry-After. Wait that many seconds before you read again.
  • A Prefer or since_version value that isn’t valid gets 422 invalid_input.
  • Webhooks push each change to your server instead.
  • Over MCP, get_quote_request takes wait_seconds in place of the header, and waits 20 seconds when you leave it out. Read status in each result. While it is in_progress, call get_quote_request again with the same id, wait_seconds: 20, and since_version set to the version you just read. If you read by handle, send the same read_handle too.

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:

version=1 # the version in the 202 answer
while true; do
body=$(curl -s -D headers.txt "https://api.truvo.com/v1/quote-requests/$QUOTE_REQUEST_ID?since_version=$version" \
-H "Authorization: Bearer $TRUVO_API_KEY" \
-H "Truvo-Version: 2026-09-26" \
-H "Prefer: wait=20")
state=$(printf '%s' "$body" | jq -r .status)
printf '%s\n' "$body" | jq -c '{status, quotes}'
if [ "$state" != "in_progress" ]; then
break
fi
version=$(printf '%s' "$body" | jq -r .version)
retry_after=$(awk -F': ' 'tolower($1) == "retry-after" { print $2 + 0 }' headers.txt)
if [ -n "$retry_after" ]; then
sleep "$retry_after"
fi
done

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, and POST /v1/event-subscriptions/{id}/replay require the Idempotency-Key header. 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 id and its current status, version, and quotes, so the answer can differ from the first one.
  • The same key with a different body gets 409 request_key_reused, with the retry action use_new_request_key.
  • A key’s scope is one environment, one integration, one resource, and one operation.
  • Over MCP, request_key carries 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.

{
"error": {
"code": "needs_information",
"message": "The quote request needs more information.",
"field_errors": [
{
"path": "application.applicant.email",
"message": "This fact is required.",
"code": "required"
}
],
"retry_action": {
"type": "supply_facts",
"summary": "Supply the missing facts and send the request again."
},
"doc_url": "https://docs.truvo.com/v1/errors/needs_information"
},
"request_id": "req_..."
}

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.

CodeHTTPWhat it means
invalid_json400The request body isn’t valid JSON.
invalid_cursor400The cursor isn’t one that this list or feed returned. It applies to every paged read: GET /v1/quote-requests, GET /v1/requests, GET /v1/event-subscriptions, and the event feed, GET /v1/events. Send a next_cursor from an earlier page of the same read, or leave the cursor out to start again.
invalid_version400The Truvo-Version header is missing, names no revision that Truvo serves, or names a revision that Truvo retired. The message says which revision to send, and field_errors[0].allowed_values lists each revision that Truvo serves, newest first. Send one of them exactly. See Versions and changes. On /v1/mcp, the code means that the MCP-Protocol-Version header names an MCP protocol version that Truvo doesn’t serve. The answer is a JSON-RPC error, so only the request log, GET /v1/requests or list_api_requests, shows the code. An MCP client that falls back to an older protocol version and connects, as Claude Code does, needs no action.
authentication_failed401The key is missing or isn’t valid.
forbidden403The key doesn’t have authority for this action.
organization_suspended403Truvo suspended your organization’s sandbox or live access. Contact Truvo support.
live_access_not_approved403This live key, or the organization of a signed-in member, does not have access to live operations. The reason is pending_review. To get live access, book a call from the Go live page of the portal.
wrong_key_kind403This key can’t access this endpoint.
not_found404The resource doesn’t exist, or it’s outside your authority.
conflict409The resource’s current state doesn’t allow this request. For example, another call with this request key is still running, a subscription create didn’t finish in time, another rotation of the subscription is in progress, the integration holds the most active subscriptions it may hold, the subscription is disabled, or delivery to it is off.
request_key_reused409This request key was used with different input. Send a new key.
version_conflict409A newer version exists. Read it before sending the request again.
quote_expired409Not returned yet. The quote has expired.
price_changed409Not returned yet. The quote has a newer version with a different price.
purchase_not_supported409Not returned yet. The quote has no purchase through the API.
event_cursor_expired410The event feed, GET /v1/events, keeps events for 30 days. It can’t resume from this cursor because retention deleted an event after it. Read each resource you track, then read the feed again without a cursor.
request_too_large413The request body is too large.
invalid_input422The input isn’t valid. field_errors lists the fields. When a quote request body matches the schema but Truvo refuses a field, field_errors also lists each missing fact after the refused fields, with the message This fact is required. A quote request in a state where Truvo can’t quote the line gets this code too, in the sandbox and live. field_errors names the state field. GET /v1/capabilities lists the states.
needs_information422Required facts are missing. Truvo returns this before it asks any carrier.
rate_limited429A rate limit stopped the request. The message names the limit, and the Retry-After header gives the seconds to wait.
internal_error500Something went wrong inside Truvo.
service_unavailable503A service that Truvo needs is down. Try the request again later, after the seconds in the Retry-After header.

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.

Retry actionWhat to do
noneDon’t send the request again.
retryTry the request again later.
correct_inputFix the fields in field_errors, then send the request again.
supply_factsAdd the missing facts in field_errors, then send the request again.
read_statusRead the resource again.
use_new_request_keySend the request with a new Idempotency-Key.
waitWait, then send the request again.
contact_supportContact Truvo support at partners@truvo.com with the request_id.

When to retry

  • A timeout or a network error. Send the same body with the same request key.
  • 503 service_unavailable. Wait for the time in Retry-After, then send the same body with the same key.
  • 500 internal_error. Send the same body with the same key.
  • 429 rate_limited. Wait for the time in Retry-After, then send the request again.
  • 409 request_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-Version on 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/mcp and the discovery documents (/v1/openapi.json, /v1/openapi/<revision>.json, /v1/revisions, /llms.txt and /.well-known/api-catalog) don’t need the header.
  • GET /v1/revisions needs no key. It lists the current revision and every revision with its status (current, supported or retired), newest first. /v1/openapi/<revision>.json is the OpenAPI document of one served revision.
  • A missing header, or one that names no revision that Truvo serves, gets 400 invalid_version. Its message says which revision to send, and field_errors[0].allowed_values lists 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/mcp always 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/events is 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 Deprecation and Sunset headers. After the end date, a request that names the revision gets 400 invalid_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 status and retry_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 404 uses the current revision. So does a refusal that Truvo sends before it knows your revision: 401, 413, a 429 for failed authentication, or 400 invalid_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.version or a quote’s version, 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-subscriptions with an Idempotency-Key, an https url on a public host, and the exact event_types you want. There’s no wildcard. The answer shows the signing_secret once, so store it right away. Only a server key can manage subscriptions, and each integration can hold 10 active subscriptions in each environment. PATCH and DELETE on /v1/event-subscriptions/{id} change or disable one, and POST /v1/event-subscriptions/{id}/rotate replaces the secret. The old secret keeps working for 24 hours.
  • Verify. Each delivery carries the webhook-id, webhook-timestamp, and webhook-signature headers. The signature is an HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{body}, keyed with the part of your signing secret after whsec_, 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. The standardwebhooks package on npm also refuses a timestamp more than 5 minutes from your clock.
  • Retries. Answer 2xx within 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 send PATCH with your URL. The same event can arrive more than once and out of order, so skip an id you’ve already processed.
  • Thin events. An event names the resource that changed, in resource.type, resource.id, and resource.version, and it carries no customer data. Read the resource to see what changed, and never apply an older resource.version over a newer one. Ignore an event type or a field you don’t know, and still answer 2xx.
  • Resync through the feed. GET /v1/events holds every event of the last 30 days, with the same id as its webhook. Each page has events, has_more, and next_cursor. Page through it with cursor and limit (10 by default, 100 at most), and store each next_cursor only after your work is saved. While has_more is true, send next_cursor as cursor to read the next page. When has_more is false, you’ve caught up; keep next_cursor and read again later. After event_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.

ToolWhat it does
get_capabilitiesShows what the key can quote for one line. Its required_facts_schema is the JSON Schema of the application for that line.
start_quote_requestStarts one quote request. Its input lists application as an object with no fields, so build the application from the required_facts_schema that get_capabilities returns. Truvo checks the application as it does over HTTP. It requires a request_key.
get_quote_requestReads a quote request, with each quote inline. It waits up to wait_seconds, 20 by default, for the next change, counted from since_version when you send it. Read status; while it is in_progress, call again with the same id, wait_seconds: 20, and since_version set to the version you just read. If you read by handle, send the same read_handle too.
list_quote_requestsLists the key’s quote requests, newest first. It filters by status, line, created_after, created_before, or updated_since, and pages with limit and cursor.
get_quoteReads one quote. Leave out version to read the latest version.
send_feedbackSends a feedback report, once the person agrees. The server always lists it.
list_api_requestsLists the integration’s recent requests, newest first, with each request’s status, error code, and request ID.
send_test_eventSends a test event to one webhook subscription. It requires a request_key.
list_event_deliveriesLists one subscription’s deliveries from the last 30 days, newest first.
get_event_deliveryReads one delivery, with its attempts.
replay_event_deliverySends an event from the delivery log again, with the same event ID. It requires a request_key.
get_event_subscription_healthShows whether delivery to one subscription works.

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, or other. Required.
  • summary: one line, up to 200 characters, that says what went wrong or what’s missing. Required.
  • expected and actual: what you expected, and what happened instead, up to 2,000 characters each. Required.
  • tried: the operation or MCP tool you called, and the request_ids of up to 20 calls that show the problem.
  • agent: your agent’s name, such as Claude Code, and its version.

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.

  • Quickstart: the same first quote, step by step.
  • Agent setup: connect an MCP client, and an AGENTS.md block for your repo.
  • Webhooks: subscriptions, signatures, retries, event types, and the event feed.
  • Versions and changes: revisions, the Truvo-Version header, 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 .md to any page URL to get its Markdown.

Common questions