Introduction
The Quomation Quote API is a personal auto insurance rating API for insurance agencies and software partners integrating with AgencyStream. Submit one risk in JSON or ACORD XML to request quotes from carriers available to your agent in the rating state. Retrieve per-carrier results by polling or through a signed HTTPS webhook.
What it does
You send a risk: the applicant, drivers, vehicles and the coverage wanted. We send it to each carrier enabled for your agent in the rating state, in that carrier's own format, and report every answer back in one shape — JSON or ACORD XML, whichever you sent.
Every quote is also created in the agency's AgencyStream account, so the agency sees the same customer, drivers, vehicles and results as if it had been entered by hand.
What it does not do
It does not give you carriers. The carriers quoted are the ones set up for the agent your credentials belong to, with that agent's carrier logins. A carrier missing there is missing here.
It does not bind. A quote is a price and a quote number. Some carriers also return a link to finish in their portal.
It does not cover other lines yet. Personal auto only.
How a quote flows
- Get a token with your client id and secret.
- Submit the risk. You get
202 Acceptedand ajobIdstraight away. - Poll the job every few seconds — each poll lists every carrier, finished or not, until the job is complete — or let us call you: with a callback URL the finished result is POSTed to your server.
Quoting is asynchronous. Results never arrive in the response that started them — poll for them, or receive them at your callback URL.
Before starting
| You need | Where it comes from |
|---|---|
| Client id and secret | Issued by Quomation per agency and environment. See Authentication. |
| Carriers set up for your agent | Carrier logins in the agency's AgencyStream account. |
| An environment | Sandbox for development, production for live quotes. See Environments. |
Where to go next
| Page | What it covers |
|---|---|
| Quickstart | The shortest path from credentials to prices. |
| Getting the most carriers | What to send so every carrier that can quote, does. |
| Quote lifecycle | Job statuses, carrier outcomes, when to stop polling. |
| JSON request body | Every field, with what is required. |
Environments
Two environments on separate hosts, with separate credentials.
| Environment | Base URL | Reaches |
|---|---|---|
| Sandbox | https://testapi.agencystream.com | Each carrier's own test system. |
| Production | https://api.agencystream.com | Live carriers. Real quotes. |
Sandbox is not a mock. Carriers run their test systems on their own schedule, so premiums, eligibility and downtime can differ from production. That is the carrier, not your integration.
Credentials belong to one environment
A token carries its environment. Sandbox credentials on the production host, or the reverse, get 403 QM_WRONG_ENVIRONMENT.
Response headers
| Header | Meaning |
|---|---|
X-Quomation-Environment | sandbox or production. A call to the wrong host is obvious at once. |
X-Quomation-Request-Id | Identifies this call in our logs. Quote it in support requests. |
Authentication
OAuth 2.0 client credentials: exchange your client id and secret for a short-lived bearer token, then send the token on every call.
Getting credentials
We issue a client_id and client_secret per agency and environment and send you a one-time link that shows them once. Open it yourself, save both values, and do not forward the link.
We cannot read or resend the secret. If it is lost we issue new credentials. If the link says it was already used and you did not open it, tell us — the credentials are treated as exposed and reissued.
Getting a token
Call POST /api/auth with HTTP Basic authentication and grant_type=client_credentials. Send the returned token as Authorization: Bearer <token> on every other call.
Request
curl -X POST https://testapi.agencystream.com/api/auth
-u "$CLIENT_ID:$CLIENT_SECRET"
-d grant_type=client_credentials
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"environment": "sandbox"
}
Token lifetime
- Read
expires_in(seconds; currently one hour) and reuse the token until it is nearly up. Do not fetch one per request. - On
401, get a new token and retry the call once. - Nothing in the request body authenticates you. An ACORD
SignonRqis optional and ignored.
Rotating credentials
Ask for a second client, switch your integration to it, then have the first one revoked. Revoking stops new tokens at once; tokens already issued stop working when they expire.
Quickstart
From credentials to prices in four steps, on sandbox.
Before starting: a sandbox client id and secret, and a risk in the rating state your agent quotes. The example uses JSON; the ACORD flow is identical with /api/acord/... paths.
1. Get a token
BASE=https://testapi.agencystream.com
TOKEN=$(curl --fail --silent --show-error "$BASE/api/auth"
--user "$CLIENT_ID:$CLIENT_SECRET"
--data 'grant_type=client_credentials' | jq -er '.access_token') || exit 1
2. Submit the risk
Save the request body as quote.json and post it. Set the sample policy dates for your sandbox test. Generate and save one Idempotency-Key per intended quote and reuse it for retries of that same request.
# Generate this once and reuse it for retries of the same quote.
KEY=$(uuidgen)
ACCEPTED=$(curl --fail --silent --show-error "$BASE/api/quotes/auto"
-H "Authorization: Bearer $TOKEN"
-H 'Content-Type: application/json'
-H "Idempotency-Key: $KEY"
--data-binary @quote.json) || exit 1
JOB=$(printf '%sn' "$ACCEPTED" | jq -er '.jobId') || exit 1
The response is 202 Accepted with the jobId and totalCarriers. No carrier has answered yet.
3. Poll until done
# Stop on a terminal job status or when the local polling budget expires.
for ((attempt = 1; attempt <= 165; attempt++)); do
RES=$(curl --fail --silent --show-error --max-time 15
"$BASE/api/quotes/$JOB" -H "Authorization: Bearer $TOKEN") || exit 1
case "$(printf '%sn' "$RES" | jq -er '.status')" in
completed|partiallyCompleted)
printf '%sn' "$RES" | jq '.carriers'
break
;;
pending|inProgress) sleep 4 ;;
*) printf 'Unexpected quote status for job %sn' "$JOB" >&2; exit 1 ;;
esac
done
if (( attempt > 165 )); then
printf 'Polling limit reached. Resume with job %sn' "$JOB" >&2
fi
Carrier results become available as each carrier responds. Save the job ID so you can resume polling if the local polling limit is reached.
4. Read the results
echo "$RES" | jq -r '.carriers[] | select(.status=="quoted")
| .carrier as $c | .plans[] | "(.fullTermPremium)t($c)t(.name // "")"' | sort -n
Each carrier has a status: quoted, bridge, declined, failed or notAvailable. A mix is normal — see Quote lifecycle.
What next
| Page | Why |
|---|---|
| Getting the most carriers | Turn declines and failures into quotes. |
| JSON request body | Every field you can send. |
| Callbacks | Have results POSTed to you instead of polling. |
| Errors | What each error means and what to do. |
Getting the most carriers
Most carrier failures come from a missing or unusual value, not from the carrier being down. Send this and every carrier that will write the risk returns a price.
Carriers
- Do not name carriers. Leave out
carriers/com.quomation_CarrierSelectionto quote every carrier enabled for your agent in the state. A list only narrows that, and a misspelled name comes backnotAvailable.
Coverages
- Send every coverage explicitly: bodily injury, property damage, uninsured motorist (or an explicit rejection), PIP or medical payments where the state has them, and comprehensive and collision deductibles for physical damage.
- Use the state's standard limits in dollars, e.g.
30000/60000. Keep UM at or below BI. - Missing or unusual limits are the top cause of “coverage required” and “no coverages” failures.
Applicant and drivers
- Prior insurance whenever the applicant has it: carrier, expiration date, months with that carrier, prior BI limit. One of the largest rating factors; without it carriers are sent a placeholder prior policy and the premium is only indicative.
- Every driver complete: name, date of birth, gender, marital status, relationship, licence number, licence state, date first licensed, licence status. Several carriers refuse a driver without a licence number.
- List excluded household members with status
excluded. - Five-digit ZIP, a 10-digit US phone number, and an address in the rating state.
Vehicles
- Send the VIN (17 characters), use and annual mileage. Year, make and model work but rate less exactly.
Polling and failures
- Poll until the job is complete. A carrier still
runningis not a failure; dropped connections are retried for you. - Read the reason.
declinedis the carrier's underwriting answer.failedwith “producer code”, “CustId not found” or “not authorized” means the agency's login with that carrier needs fixing — send us the request id. - Effective date today or soon. Most carriers will not quote far-dated policies.
Your carrier list is your agent's list. Credentials are tied to an agent; you get the carriers that agent sees in AgencyStream, rated with that agent's logins. If a carrier is missing, check it is enabled for that agent and state.
Quote lifecycle
A submitted quote is a job with one leg per carrier. The job finishes when every leg has a final answer.
Job status
| JSON | ACORD | Meaning |
|---|---|---|
pending | Pending | Accepted, no carrier started yet. Keep polling. |
inProgress | InProgress | Carriers are answering. Keep polling. |
completed | Completed | Every carrier has a final answer. Stop. |
partiallyCompleted | PartiallyCompleted | We stopped waiting; carriers without an answer are marked failed. Stop. |
Carrier status
| JSON | ACORD | Meaning |
|---|---|---|
pending, running | StatusCd Pending / Running | Not finished yet. |
quoted | StatusCd 0, MsgStatusCd Success | At least one priced plan. |
bridge | StatusCd 0, com.quomation_BridgeURL | No price through the API; finish in the carrier portal. |
declined | StatusCd 0, MsgStatusCd Rejected | The carrier refused the risk. Retrying will not help. |
failed | StatusCd 502 / 504 | No answer: timeout, carrier error or a carrier login problem. |
notAvailable | StatusCd 403 | Carrier not enabled for this agency, agent or state. |
When to stop polling
Poll every 3–5 seconds while the job is pending or inProgress. Stop when it is completed or partiallyCompleted, or pause when your client-side polling deadline expires.
Timeouts and retries
Each carrier gets up to 60 seconds per attempt. A connection error is retried up to 2 more times (3 attempts in all); a timeout or a decline is final. A carrier call that was interrupted on our side is marked failed after 5 minutes, and a periodic check closes jobs that remain open past the ten-minute threshold as partiallyCompleted.
Plans and payment options
A carrier can return several plans (programs). Each plan has its own premium, limits and ways to pay.
| Level | JSON | ACORD |
|---|---|---|
| Carrier | one entry in carriers[] | one InsuranceSvcRs, keyed by SPName |
| Plan | plans[] | one PersAutoPolicyQuoteInqRs per plan |
| Way to pay | paymentOptions[] | PaymentOption |
Paid in full and instalments are two ways to buy the same plan, never two plans. Compare carriers on fullTermPremium (FullTermAmt); an instalment totalPayable can be higher because of carrier fees.
A plan also reports the limits it was actually rated at (ratedLimits), which can differ from what was asked. They use AgencyStream's notation in thousands of dollars — 30/60 for 30000/60000, 25 for 25000 — unlike the request, which is in dollars.
Bridge links are single-use and short-lived. Treat them as credentials: do not log or share them.
Idempotency
Use one unique key per intended quote and reuse it when retrying the same submission.
| Format | Key |
|---|---|
| JSON | Idempotency-Key header, any unique string up to 100 characters. |
| ACORD | InsuranceSvcRq/RqUID, required. A new UUID per quote attempt. |
Once a job has been saved, a repeat with the same agency and key returns the original jobId and retains its callback URL. Keep the body and callback URL the same, and avoid concurrent submissions with the same key. After a queue-related 503, wait about one minute and retry with the original key. Some carriers may already be queued.
Callbacks
Instead of polling, have the finished result POSTed to your server. Callbacks are optional and come on top of polling: GET keeps working whatever happens to a callback.
Turning it on
| Where | Applies to |
|---|---|
?callbackUrl=https://… on POST /api/quotes/auto or POST /api/acord/auto | This quote only. Wins over the default. |
| Default callback URL of your API client, set by Quomation on request | Every quote submitted without callbackUrl. |
| Neither | No callback. Poll as usual. |
The URL is fixed when the quote is accepted. Changing the default later does not redirect quotes already in flight, and a retry with the same idempotency key keeps the original job and its URL. The JSON 202 response echoes the URL that will be used in callbackUrl.
The URL must use https on port 443 and a public host name: no user name or password in it, no localhost or private-network addresses. Redirects are not followed — register the final URL. A URL that breaks these rules is rejected with 400 QM_INVALID_CALLBACK_URL before anything is created.
What we send
When the job becomes completed or partiallyCompleted, we POST exactly the body GET would return at that moment: JSON for quotes submitted as JSON, ACORD XML for quotes submitted as ACORD. One parser handles both the poll and the callback.
| Header | Value |
|---|---|
X-Quomation-Event | quote.job.completed (ping for a test). |
X-Quomation-Event-Id | evt_ + the job id. The same on every attempt — use it to drop duplicates. |
X-Quomation-Job-Id | The job id. |
X-Quomation-Environment | sandbox or production. |
X-Quomation-Delivery-Attempt | 1, 2, … |
X-Quomation-Timestamp | Unix time (seconds) this attempt was sent. |
X-Quomation-Signature | v1= + hex HMAC-SHA256. See below. |
Callback request
POST /quomation/callback HTTP/1.1
Host: partner.example.com
Content-Type: application/json; charset=utf-8
User-Agent: Quomation-Callbacks/1.0
X-Quomation-Event: quote.job.completed
X-Quomation-Event-Id: evt_3b8f1c2e9d4a4e61a7b052c9e1f8d403
X-Quomation-Job-Id: 3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403
X-Quomation-Environment: sandbox
X-Quomation-Delivery-Attempt: 1
X-Quomation-Timestamp: 1790865171
X-Quomation-Signature: v1=5d41402abc4b2a76b9719d911017c592a3f1e0c7b8d9e4f6a2b3c4d5e6f70812
{ "jobId": "3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403", "status": "completed", ... }
Verifying the signature
Get your signing secret from GET /api/callbacks/signing-secret. It is not your client secret. To verify a callback:
- Take
X-Quomation-Timestamp, a dot and the raw request body, before any parsing. - Compute HMAC-SHA256 over it with the secret (as UTF-8 text) as the key, hex-encode it and put
v1=in front. - Compare with
X-Quomation-Signaturein constant time, and reject timestamps more than 5 minutes away from your clock.
Reject a callback that fails the check with 401. It did not come from us.
Python
import hmac, hashlib, time
def verify(secret, headers, raw_body):
ts = headers["X-Quomation-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False # too old: replayed or delayed
expected = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, headers["X-Quomation-Signature"])
Node.js
const crypto = require("crypto");
// rawBody: the request body as a Buffer, before any JSON/XML parsing
function verify(secret, headers, rawBody) {
const ts = headers["x-quomation-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = "v1=" + crypto.createHmac("sha256", secret)
.update(ts + ".").update(rawBody).digest("hex");
const given = String(headers["x-quomation-signature"] || "");
return given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
C# (.NET Core 2.1+)
static bool Verify(string secret, string timestamp, string signature, byte[] rawBody)
{
long ts;
if (!long.TryParse(timestamp, out ts) ||
Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300) return false;
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
byte[] prefix = Encoding.UTF8.GetBytes(timestamp + ".");
hmac.TransformBlock(prefix, 0, prefix.Length, null, 0);
hmac.TransformFinalBlock(rawBody, 0, rawBody.Length);
string expected = "v1=" + BitConverter.ToString(hmac.Hash).Replace("-", "").ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(signature ?? ""));
}
}
Answering and retries
Answer within 10 seconds with any 2xx, then do the work. A slow answer counts as a failed attempt.
| Your answer | What happens |
|---|---|
2xx | Delivered. Done. |
408, 429, 5xx, timeout, connection or TLS error | Retried. |
3xx, any other 4xx | Not retried. The callback is marked failed. |
Retries come 30 seconds, 2 minutes, 10 minutes, 30 minutes, 2 hours and 6 hours after the previous attempt: 7 attempts over about 8.7 hours. Delivery is at least once — a callback whose 2xx was lost on the way arrives again with the same X-Quomation-Event-Id.
Delivery status
GET shows how delivery is going: the callback object in JSON, com.quomation_Callback in ACORD. Both are absent for a quote without a callback.
| JSON | ACORD | Meaning |
|---|---|---|
pending | Pending | The job is still running. |
queued | Queued | The job finished; the callback is being sent. |
retrying | Retrying | An attempt failed; another one is scheduled. |
delivered | Delivered | Your server answered 2xx. |
failed | Failed | Given up. The result is still available by polling. |
Because the callback body is the GET response at the moment of sending, it carries this block too, with queued or retrying and the attempts made so far.
Fixed your endpoint after a failed callback? Resend it. To check your endpoint and signature code without a real quote, send a test callback.
Get a token
https://testapi.agencystream.com/api/authExchanges client credentials for a bearer token. Errors use the OAuth format (error, error_description).
Request
AuthorizationheaderrequiredBasic base64(client_id:client_secret). Credentials in the form body are also accepted.Content-Typeheaderrequiredapplication/x-www-form-urlencodedgrant_typeformrequiredclient_credentials.Response
access_tokenstringAuthorization: Bearer ....token_typestringBearer.expires_inintegerenvironmentstringsandbox or production.unsupported_grant_type: grant type is not client_credentials.invalid_client: unknown, wrong, revoked or expired credentials.server_error: credentials could not be checked just now. Retry shortly.Request
curl -X POST https://testapi.agencystream.com/api/auth
-u "$CLIENT_ID:$CLIENT_SECRET"
-d grant_type=client_credentials
200 Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"environment": "sandbox"
}
401 Response
{
"error": "invalid_client",
"error_description": "The client credentials are invalid, revoked or expired."
}
Submit a quote (JSON)
https://testapi.agencystream.com/api/quotes/autoValidates the risk, creates the quote in AgencyStream and starts rating every eligible carrier. Returns before any carrier has answered — poll GET /api/quotes/{jobId} for results.
Request
AuthorizationheaderrequiredBearer <token>Content-Typeheaderrequiredapplication/jsonIdempotency-KeyheadercallbackUrlquery, urlbodyobjectrequiredResponse
jobIduuidtotalCarriersintegerstatusUrlurlrequestIdstringX-Quomation-Request-Id.environmentstringsandbox or production.submittedAtdatetimecallbackUrlurlerrors[], including QM_INVALID_CALLBACK_URL for a rejected callbackUrl.QM_QUEUE_UNAVAILABLE: queue briefly unavailable. Retry in a minute with the same idempotency key.Request
curl -X POST https://testapi.agencystream.com/api/quotes/auto
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-H "Idempotency-Key: 5f0d8c3e-2a41-4d7b-9c1e-7e2b6a9d4f10"
--data-binary @quote.json
202 Response
{
"requestId": "7f3c2a91-4e88-4d1b-9a02-6b5c1d0e4477",
"environment": "sandbox",
"jobId": "3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403",
"totalCarriers": 5,
"submittedAt": "2026-10-01T14:32:10Z",
"statusUrl": "https://testapi.agencystream.com/api/quotes/3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403"
}
400 Response
{
"statusCode": 400,
"requestId": "7f3c2a91-4e88-4d1b-9a02-6b5c1d0e4477",
"errors": [
{ "errorCode": "QM_FIELD_REQUIRED", "errorMessage": "is required.",
"field": "drivers[0].licenseState" },
{ "errorCode": "QM_FIELD_INVALID", "errorMessage": "effectiveDate must be today or a future date.",
"field": "effectiveDate" }
]
}
Get quote results (JSON)
https://testapi.agencystream.com/api/quotes/{jobId}The job as it stands now: every carrier, finished or still running. Poll until status is completed or partiallyCompleted.
Request
jobIdpath, uuidrequiredAuthorizationheaderrequiredBearer <token>. Only the agency that created the job can read it.Response
statusstringpending | inProgress | completed | partiallyCompleted.totalCarriersintegercarriers[].summaryobjectcarriersQueried, carriersQuoted, plansReturned, declined, failed, bridgeOnly, lowestPremium, lowestPremiumCarrier, elapsedMs.carriers[].carrierstringcarriers[].statusstringcarriers[].reasonstringcarriers[].bridgeUrlurlcarriers[].carrierRequestId, elapsedMs, messages[]carriers[].plans[]arrayname, fullTermPremium, term, quoteNumber, ratedLimits, paymentOptions[], messages[].…paymentOptions[]arraymethod (paidInFull | installments), downPayment, installmentAmount, installmentCount, totalPayable.callbackobjecturl, status, attempts, lastAttemptAt, lastHttpStatus, lastError, deliveredAt. See Delivery status.Request
curl https://testapi.agencystream.com/api/quotes/$JOB_ID
-H "Authorization: Bearer $TOKEN"
200 Response
{
"jobId": "3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403",
"status": "completed",
"totalCarriers": 5,
"submittedAt": "2026-10-01T14:32:10Z",
"completedAt": "2026-10-01T14:32:51Z",
"summary": {
"carriersQueried": 5, "carriersQuoted": 2, "plansReturned": 2,
"declined": 1, "failed": 1, "bridgeOnly": 1,
"lowestPremium": 1042.75, "lowestPremiumCarrier": "Mercury"
},
"carriers": [
{
"carrier": "Mercury",
"status": "quoted",
"carrierRequestId": "18342", "elapsedMs": 21400,
"plans": [
{
"name": "Preferred",
"fullTermPremium": 1042.75,
"term": "6 Months",
"quoteNumber": "MC-5510221",
"ratedLimits": { "bodilyInjury": "30/60", "propertyDamage": "25",
"comprehensiveDeductible": "500", "collisionDeductible": "500" },
"paymentOptions": [
{ "method": "paidInFull", "downPayment": 0, "installmentAmount": 0,
"installmentCount": 1, "totalPayable": 1042.75 },
{ "method": "installments", "description": "6 Pay", "downPayment": 208.55,
"installmentAmount": 173.80, "installmentCount": 5, "totalPayable": 1077.55 }
],
"messages": []
}
]
},
{ "carrier": "Stillwater", "status": "quoted", "plans": [ { "fullTermPremium": 1184.00, "paymentOptions": [] } ] },
{ "carrier": "Hallmark", "status": "declined",
"reason": "Risk outside appetite: violation within 36 months", "plans": [] },
{ "carrier": "Mendota", "status": "failed",
"reason": "Timed out waiting for carrier.", "plans": [] },
{ "carrier": "Connect Insurance", "status": "bridge",
"bridgeUrl": "https://portal.example-carrier.com/sso?token=abc123", "plans": [] }
],
"callback": {
"url": "https://partner.example.com/quomation/callback",
"status": "delivered", "attempts": 1, "lastHttpStatus": 200,
"lastAttemptAt": "2026-10-01T14:32:52Z", "deliveredAt": "2026-10-01T14:32:52Z"
}
}
Submit a quote (ACORD)
https://testapi.agencystream.com/api/acord/autoThe same as the JSON submit, with an ACORD P&C PersAutoPolicyQuoteInqRq document. Poll GET /api/acord/quotes/{jobId} for results.
Request
AuthorizationheaderrequiredBearer <token>Content-Typeheaderrequiredapplication/xml, UTF-8.bodyACORD XMLrequiredInsuranceSvcRq/RqUID is the idempotency key.callbackUrlquery, urlResponse
An ACORD document: SignonRs/Status/StatusCd 0, plus com.quomation_JobId and com.quomation_TotalCarriers. Errors put one Status per problem after SignonRs.
callbackUrl is reported against com.quomation_CallbackUrl.RqUID.QM_QUEUE_UNAVAILABLE: queue briefly unavailable. Retry in a minute with the same RqUID.Request
curl -X POST https://testapi.agencystream.com/api/acord/auto
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/xml"
--data-binary @quote.xml
202 Response
<?xml version="1.0" encoding="utf-8"?>
<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
<SignonRs>
<Status><StatusCd>0</StatusCd><StatusDesc>Success</StatusDesc></Status>
<ServerDt>2026-10-01T14:32:10Z</ServerDt>
</SignonRs>
<com.quomation_JobId>3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403</com.quomation_JobId>
<com.quomation_TotalCarriers>4</com.quomation_TotalCarriers>
</ACORD>
400 Response
<?xml version="1.0" encoding="utf-8"?>
<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
<SignonRs>
<Status><StatusCd>400</StatusCd><StatusDesc>Request validation failed</StatusDesc></Status>
<ServerDt>2026-10-01T14:32:10Z</ServerDt>
</SignonRs>
<Status>
<StatusCd>400</StatusCd>
<StatusDesc>InsuranceSvcRq/RqUID is required and must be unique per quote attempt.</StatusDesc>
</Status>
<Status>
<StatusCd>400</StatusCd>
<StatusDesc>PersVeh[@id='V1']/Coverage[CoverageCd='BI'] bodily injury liability is required.</StatusDesc>
</Status>
</ACORD>
Get quote results (ACORD)
https://testapi.agencystream.com/api/acord/quotes/{jobId}One InsuranceSvcRs per carrier, always in the same order (the order the job started the carriers), finished or not. Key them on SPName, never on position.
Response
com.quomation_JobStatusstringPending | InProgress | Completed | PartiallyCompleted.com.quomation_TotalCarriersintegerInsuranceSvcRs blocks.InsuranceSvcRs/Status/StatusCdstring0 answered; Pending/Running not yet; 403, 502, 504 no answer.InsuranceSvcRs/SPNamestring…/MsgStatus/MsgStatusCdstringSuccess, SuccessWithInfo or Rejected (declined).…/MsgStatus/ExtendedStatusrepeated…/CompanyProductCdstring…/PolicySummaryInfo/FullTermAmt/Amtdecimal…/PolicySummaryInfo/PolicyNumberstring…/PaymentOptionrepeatedMethodPaymentCd, DownPaymentAmt, InstallmentInfo, com.quomation_TotalPayableAmt.…/com.quomation_BridgeURLurlcom.quomation_CallbackelementUrl, StatusCd, Attempts, LastAttemptDt, LastHttpStatus, LastError, DeliveredDt. See Delivery status.Request
curl https://testapi.agencystream.com/api/acord/quotes/$JOB_ID
-H "Authorization: Bearer $TOKEN"
200 Response
<?xml version="1.0" encoding="utf-8"?>
<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
<SignonRs>
<Status><StatusCd>0</StatusCd><StatusDesc>Success</StatusDesc></Status>
<ServerDt>2026-10-01T14:32:31Z</ServerDt>
</SignonRs>
<com.quomation_JobId>3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403</com.quomation_JobId>
<com.quomation_JobStatus>InProgress</com.quomation_JobStatus>
<com.quomation_TotalCarriers>4</com.quomation_TotalCarriers>
<!-- quoted -->
<InsuranceSvcRs>
<Status><StatusCd>0</StatusCd><StatusDesc>Success</StatusDesc></Status>
<SPName>Mercury</SPName>
<PersAutoPolicyQuoteInqRs>
<MsgStatus><MsgStatusCd>Success</MsgStatusCd></MsgStatus>
<CompanyProductCd>Preferred</CompanyProductCd>
<PolicySummaryInfo>
<PolicyNumber>MC-5510221</PolicyNumber>
<FullTermAmt><Amt>1042.75</Amt></FullTermAmt>
<PolicyTermCd>6 Months</PolicyTermCd>
</PolicySummaryInfo>
<PaymentOption>
<MethodPaymentCd>PaidInFull</MethodPaymentCd>
<com.quomation_TotalPayableAmt><Amt>1042.75</Amt></com.quomation_TotalPayableAmt>
</PaymentOption>
<PaymentOption>
<MethodPaymentCd>Installments</MethodPaymentCd>
<PaymentPlanCd>6 Pay</PaymentPlanCd>
<DownPaymentAmt><Amt>208.55</Amt></DownPaymentAmt>
<InstallmentInfo>
<NumPayments>5</NumPayments>
<InstallmentAmt><Amt>173.80</Amt></InstallmentAmt>
</InstallmentInfo>
<com.quomation_TotalPayableAmt><Amt>1077.55</Amt></com.quomation_TotalPayableAmt>
</PaymentOption>
</PersAutoPolicyQuoteInqRs>
</InsuranceSvcRs>
<!-- declined -->
<InsuranceSvcRs>
<Status><StatusCd>0</StatusCd><StatusDesc>Success</StatusDesc></Status>
<SPName>Hallmark</SPName>
<PersAutoPolicyQuoteInqRs>
<MsgStatus>
<MsgStatusCd>Rejected</MsgStatusCd>
<ExtendedStatus><ExtendedStatusDesc>Risk outside appetite</ExtendedStatusDesc></ExtendedStatus>
</MsgStatus>
</PersAutoPolicyQuoteInqRs>
</InsuranceSvcRs>
<!-- no answer -->
<InsuranceSvcRs>
<Status><StatusCd>504</StatusCd><StatusDesc>Timed out waiting for carrier.</StatusDesc></Status>
<SPName>Mendota</SPName>
</InsuranceSvcRs>
<!-- still running: keep polling -->
<InsuranceSvcRs>
<Status><StatusCd>Running</StatusCd><StatusDesc>Not yet complete.</StatusDesc></Status>
<SPName>Stillwater</SPName>
</InsuranceSvcRs>
</ACORD>
Get the signing secret
https://testapi.agencystream.com/api/callbacks/signing-secretThe secret your callbacks are signed with. Each API client has its own, separate from its client secret.
Request
AuthorizationheaderrequiredBearer <token>Response
clientIdstringversionintegersecretstringqcs_…. Use it as the HMAC key exactly as given. Keep it on your server.algorithm, signedPayload, signatureHeaderstringdefaultCallbackUrlurlnull.Request
curl https://testapi.agencystream.com/api/callbacks/signing-secret
-H "Authorization: Bearer $TOKEN"
200 Response
{
"clientId": "qm_test_id_8f2c41d07a",
"version": 1,
"secret": "qcs_Zk3n9Qv0b7Xr2LmT5yW8pA1sD4fG6hJ0kL3zX9cV2bN",
"algorithm": "HMAC-SHA256",
"signedPayload": "{X-Quomation-Timestamp}.{raw request body}",
"signatureHeader": "X-Quomation-Signature: v1=<hex>",
"defaultCallbackUrl": "https://partner.example.com/quomation/callback"
}
Rotate the signing secret
https://testapi.agencystream.com/api/callbacks/signing-secret/rotateReplaces the signing secret and returns the new one, in the same shape as Get the signing secret.
The old secret stops working at once: every callback sent after the rotation is signed with the new one. Update your receiver straight away.
Request
AuthorizationheaderrequiredBearer <token>Response
version is one higher.Request
curl -X POST https://testapi.agencystream.com/api/callbacks/signing-secret/rotate
-H "Authorization: Bearer $TOKEN"
Send a test callback
https://testapi.agencystream.com/api/callbacks/testSends a signed ping event to your endpoint right now, the same way a real callback is sent, and tells you what your endpoint answered. Nothing is stored or retried. Use it to check your endpoint and signature code before the first real quote.
Request
AuthorizationheaderrequiredBearer <token>callbackUrlquery, urlResponse
callbackUrlurleventIdstringevt_test_…, also sent as X-Quomation-Event-Id.deliveredbooleantrue when your endpoint answered 2xx.httpStatusintegernull when it did not answer.elapsedMsintegererrorstringwouldRetrybooleandelivered: a failing endpoint is still a 200 here.QM_INVALID_CALLBACK_URL: bad callbackUrl, or none given and no default set.Request
curl -X POST "https://testapi.agencystream.com/api/callbacks/test?callbackUrl=https://partner.example.com/quomation/callback"
-H "Authorization: Bearer $TOKEN"
200 Response
{
"callbackUrl": "https://partner.example.com/quomation/callback",
"eventId": "evt_test_9a1f2b3c4d5e6f708192a3b4c5d6e7f8",
"delivered": false,
"httpStatus": 401,
"elapsedMs": 184,
"error": "HTTP 401",
"wouldRetry": false
}
Ping body your endpoint receives
{
"event": "ping",
"eventId": "evt_test_9a1f2b3c4d5e6f708192a3b4c5d6e7f8",
"createdAt": "2026-10-01T14:40:02Z",
"environment": "sandbox",
"message": "Test callback from the Quomation Quote API. Verify the signature, then answer 2xx."
}
Resend a callback
https://testapi.agencystream.com/api/callbacks/jobs/{jobId}/resendDelivers a finished callback again, from attempt 1, to the URL the quote was submitted with — typically after you fixed your endpoint. Works once delivery is over (delivered or failed).
Request
jobIdpath, uuidrequiredAuthorizationheaderrequiredBearer <token>Response
callback.status on GET.QM_CALLBACK_IN_PROGRESS: still being delivered. Wait until it is delivered or failed.QM_NO_CALLBACK: the quote was submitted without a callback URL.Request
curl -X POST https://testapi.agencystream.com/api/callbacks/jobs/$JOB_ID/resend
-H "Authorization: Bearer $TOKEN"
202 Response
{
"jobId": "3b8f1c2e-9d4a-4e61-a7b0-52c9e1f8d403",
"callbackUrl": "https://partner.example.com/quomation/callback",
"status": "queued"
}
Industries and occupations
https://testapi.agencystream.com/api/reference/occupationsThe industry and occupation titles AgencyStream uses. Carriers map an occupation only from these exact pairs, so industry / occupation (ACORD com.quomation_Industry / OccupationDesc) must come from this list; anything else is a 400. The list changes rarely — cache it for a day.
200 Response (shortened)
{
"industries": [
{ "industry": "Business/Sales/Office",
"occupations": [ "Director/Administrator", "..." ] },
{ "industry": "Education/Library", "occupations": [ "..." ] }
]
}
Violation types
https://testapi.agencystream.com/api/reference/violationsThe accident and violation types AgencyStream uses. Each carrier maps a violation to its own code by this exact description and nothing else, so drivers[].violations[].description (ACORD AccidentViolationDesc) must come from this list. Free text such as Speeding 16-20 over would reach every carrier unmapped and be declined, so it is rejected with a 400. The API also accepts the list’s code. When several types share one code, it picks the type whose numbers match the description. The quote stores the list’s own text and category. The list changes rarely — cache it for a day.
200 Response (shortened)
{
"violations": [
{ "category": "Speeding", "description": "10 - 20 MPH Over Limit", "code": "..." },
{ "category": "Accident", "description": "At Fault", "code": "..." }
]
}
JSON request body
The body of POST /api/quotes/auto. Dates are yyyy-MM-dd; enum values are case-insensitive, and a value outside the listed ones is rejected with 400.
Policy
effectiveDatedaterequiredstatestring(2)requiredapplicant.address.state.termMonthsinteger6 (default) or 12.paymentMethodenumpaidInFull | installments | eft. Changes the premium on most carriers.carriersstring[]leadSourcestringproducer.agencyNamestringproducer.producerCode, phone, addressApplicant
applicant.firstName, lastNamestringrequiredapplicant.middleName, suffixstringapplicant.dateOfBirthdaterequiredapplicant.genderenumrequiredmale | femaleapplicant.maritalStatusenumrequiredsingle | married | divorced | widowed | separated | domesticPartner | domesticPartnerUnregisteredapplicant.address.line1, city, state, zipstringrequiredapplicant.address.countystringapplicant.phone, emailstringapplicant.industry, occupationstringGET /api/reference/occupations; industry is required with occupation. Any other text is rejected: carriers only recognise these titles.applicant.educationenumnone | highSchool | vocational | someCollege | communityCollege | associates | bachelors | masters | law | medical | doctorateapplicant.monthsAtAddressintegerPrior insurance
Send whenever the applicant is insured. If it is left out, carriers are sent a standard placeholder — Other Standard Carrier, prior BI 15/30, 12 months, no lapse — the same default AgencyStream's consumer portal uses, because most carriers will not rate a risk with no prior policy at all. Premiums are then only indicative; send the real prior policy whenever there is one.
When there is no prior policy for a known reason, leave priorInsurance out and send noPriorInsuranceReason instead: didNotOwnVehicle | militaryService | vehicleInStorage | lapsedUnder30Days | lapsedOver30Days. These are the answers AgencyStream's consumer portal offers, and carriers rate each of them differently from the placeholder. Sending both is a 400.
priorInsurance.carrierNamestringrequiredpriorInsurance.expirationDatedatepriorInsurance.monthsWithCarrierintegerpriorInsurance.bodilyInjuryLimitstring30000/60000.priorInsurance.naicCode, policyNumberstringpriorInsurance.effectiveDatedateDrivers
1 to 4 drivers.
drivers[].idstringdriverAssignments, e.g. d1.drivers[].firstName, lastNamestringdrivers[].middleName, suffixstringdrivers[].dateOfBirthdaterequireddrivers[].gender, maritalStatusenumrequireddrivers[].statusenumrequiredrated | excluded | listed. listed: a household member reported to carriers but not rated.drivers[].relationshipenuminsured | spouse | child | parent | otherdrivers[].licenseStatestring(2)requireddrivers[].licenseNumberstringdrivers[].licensePermitNumberstringdrivers[].licensedDatedatedrivers[].licenseStatusenumvalid (default) | suspended | revoked | expired | permitdrivers[].industry, occupation, educationBusiness/Sales/Office, Director/Administrator, High School Diploma).drivers[].goodStudent, studentAwayAtSchoolbooleandrivers[].requiresFinancialFilingbooleandrivers[].violations[]arraycode (SPD, ACC, DWI…), description, date, convictionDate, recordType, lossAmount. code and date are required for each violation. description must be a violation type from GET /api/reference/violations (e.g. 10 - 20 MPH Over Limit, At Fault): carriers map a violation only by that exact text. When it is omitted, code is matched against the list instead. A value the list does not contain is a 400.Vehicles
1 to 4 vehicles.
vehicles[].vinstring(17)conditionalyear + make + model. VIN preferred.vehicles[].useenumrequiredpleasure | commute | business | farm | artisanvehicles[].coveragesobjectconditionalvehicles[].idstringdriverAssignments, e.g. v1.vehicles[].annualMileage, odometerintegervehicles[].garagingAddressobjectvehicles[].leasedbooleanvehicles[].antiTheftDeviceenumactiveAlarm | passiveAlarm | activeDisablingDevice | passiveDisablingDevice | recoveryDevice | vinEtching | onStarvehicles[].passiveRestraintenumairbagDriver | airbagDual | airbagDualSide | activeBelts | passiveBeltsvehicles[].bodyStyle, registrationState, antiLockBrakes, daytimeRunningLights, costNew, statedValue, purchaseDateCoverages
Limits in dollars. Split: {"perPerson": 30000, "perAccident": 60000}. Single or CSL: {"limit": 25000}. Deductible: {"deductible": 500}. Rejection: {"rejected": true}.
bodilyInjurylimitrequiredpropertyDamagelimitrequireduninsuredMotorist, underinsuredMotorist, uninsuredMotoristPropertyDamagelimitpersonalInjuryProtection, medicalPaymentslimitcomprehensive, collisiondeductiblerentalReimbursement, towinglimitloanLeaseGapbooleanleased: true.Driver assignments
driverAssignments[]arraydriverId, vehicleId, usePercent. Percentages per vehicle must total 100. Without it each carrier assigns drivers its own way.Full example
{
"effectiveDate": "2026-10-15",
"state": "TX",
"termMonths": 6,
"paymentMethod": "paidInFull",
"producer": { "agencyName": "Northwind Insurance" },
"applicant": {
"firstName": "Ryan",
"lastName": "Carter",
"dateOfBirth": "1990-03-22",
"gender": "male",
"maritalStatus": "married",
"email": "r.carter@example.com",
"phone": "512-555-0178",
"monthsAtAddress": 36,
"address": { "line1": "2100 Riverside Dr", "city": "Austin", "state": "TX", "zip": "78704" }
},
"priorInsurance": {
"carrierName": "GEICO",
"expirationDate": "2026-10-14",
"monthsWithCarrier": 24,
"bodilyInjuryLimit": "30000/60000"
},
"drivers": [
{
"id": "d1", "firstName": "Ryan", "lastName": "Carter",
"dateOfBirth": "1990-03-22", "gender": "male", "maritalStatus": "married",
"relationship": "insured", "status": "rated",
"licenseNumber": "41288902", "licenseState": "TX",
"licensedDate": "2006-05-02", "licenseStatus": "valid"
},
{
"id": "d2", "firstName": "Emma", "lastName": "Carter",
"dateOfBirth": "1991-07-09", "gender": "female", "maritalStatus": "married",
"relationship": "spouse", "status": "rated",
"licenseNumber": "55122109", "licenseState": "TX",
"licensedDate": "2008-08-19", "licenseStatus": "valid",
"violations": [ { "code": "SPD", "description": "10 - 20 MPH Over Limit", "date": "2025-06-11" } ]
}
],
"vehicles": [
{
"id": "v1",
"vin": "2T3P1RFV1MC123456",
"year": 2021,
"make": "Toyota",
"model": "RAV4",
"use": "commute",
"annualMileage": 12000,
"coverages": {
"bodilyInjury": { "perPerson": 30000, "perAccident": 60000 },
"propertyDamage": { "limit": 25000 },
"uninsuredMotorist": { "perPerson": 30000, "perAccident": 60000 },
"personalInjuryProtection": { "limit": 2500 },
"comprehensive": { "deductible": 500 },
"collision": { "deductible": 500 }
}
}
],
"driverAssignments": [
{ "driverId": "d1", "vehicleId": "v1", "usePercent": 60 },
{ "driverId": "d2", "vehicleId": "v1", "usePercent": 40 }
]
}
ACORD request
The body of POST /api/acord/auto: ACORD P&C PersAutoPolicyQuoteInqRq. The namespace is optional; SignonRq is optional and ignored.
Envelope
InsuranceSvcRq/RqUIDuuidrequiredIdempotency-Key header.InsuranceSvcRq/com.quomation_CarrierSelection/com.quomation_Carrierstring, repeatedcarriers.InsuranceSvcRq/com.quomation_LeadSourcestringleadSource.…/Producer/GeneralPartyInfo/NameInfo/CommlName/CommercialNamestringproducer.agencyName.…/Producer/ProducerInfo/ContractNumberstringproducer.producerCode.…/Producer/GeneralPartyInfo/Addr, Communications/PhoneInfo/PhoneNumberaddress, stringproducer.address, producer.phone.Applicant
Under PersAutoPolicyQuoteInqRq/InsuredOrPrincipal.
GeneralPartyInfo/NameInfo/PersonName/GivenName, Surnamestringrequiredapplicant.firstName, lastName.GeneralPartyInfo/NameInfo/PersonName/OtherGivenName, NameSuffixstringmiddleName, suffix.GeneralPartyInfo/Addr[@AddrTypeCd="InsuredsAddress"]/Addr1, City, StateProvCd, PostalCodestringrequiredGeneralPartyInfo/Addr[@AddrTypeCd="InsuredsAddress"]/CountystringGeneralPartyInfo/Communications/PhoneInfo/PhoneNumber, EmailInfo/EmailAddrstringInsuredOrPrincipalInfo/PersonInfo/BirthDtdaterequiredInsuredOrPrincipalInfo/PersonInfo/GenderCdenumrequiredM | FInsuredOrPrincipalInfo/PersonInfo/MaritalStatusCdenumrequiredS | M | D | W | P | DomesticPartner | DomesticPartnerUnregisteredInsuredOrPrincipalInfo/PersonInfo/com.quomation_Industry, OccupationDescstringGET /api/reference/occupations; industry is required with the occupation.InsuredOrPrincipalInfo/PersonInfo/EducationLevelCdenumNone | HighSchool | Vocational | SomeCollege | CommunityCollege | Associates | Bachelors | Masters | Law | Medical | DoctorateInsuredOrPrincipalInfo/LengthTimeCurrentAddr/NumUnitsintegerPolicy and prior insurance
Under PersAutoPolicyQuoteInqRq/PersPolicy.
LOBCdenumAUTOPControllingStateProvCdstring(2)requiredContractTerm/EffectiveDtdaterequiredContractTerm/DurationPeriod/NumUnitsinteger6 (default) or 12.PaymentOption/MethodPaymentCdenumPaidInFull | Installments | EFT. Changes the premium on most carriers.OtherOrPriorPolicy/InsurerNamestringconditionalOtherOrPriorPolicy/ContractTerm/ExpirationDtdateOtherOrPriorPolicy/ContractTerm/EffectiveDtdateOtherOrPriorPolicy/LengthTimeWithPreviousInsurer/NumUnitsintegerOtherOrPriorPolicy/Coverage[CoverageCd="BI"]limitOtherOrPriorPolicy/NAICCd, PolicyNumberstringcom.quomation_NoPriorInsuranceReasonenumOtherOrPriorPolicy when there is no prior policy: DidNotOwnVehicle | MilitaryService | VehicleInStorage | LapsedUnder30Days | LapsedOver30Days.Drivers
One PersAutoLineBusiness/PersDriver id="D1" per driver, up to 4.
@idstringDriverVeh, e.g. D1.GeneralPartyInfo/NameInfo/PersonName/GivenName, SurnamestringPersDriver. Send them; carriers need a name per driver.GeneralPartyInfo/NameInfo/PersonName/OtherGivenName, NameSuffixstringDriverInfo/PersonInfo/BirthDtdaterequiredDriverInfo/PersonInfo/GenderCd, MaritalStatusCdenumrequiredDriverInfo/PersonInfo/com.quomation_Industry, OccupationDesc, EducationLevelCdDriverInfo/DriversLicense/StateProvCdstring(2)requiredDriverInfo/DriversLicense/DriversLicenseNumberstringDriverInfo/DriversLicense/LicensePermitNumberstringDriverInfo/DriversLicense/LicensedDtdateDriverInfo/DriversLicense/LicenseStatusCdenumActive | Suspended | Revoked | Expired | PermitPersDriverInfo/DriverTypeCdenumrequiredRated | Excluded | Listed. Listed: reported to carriers but not rated.PersDriverInfo/DriverRelationshipToApplicantCdenumIN | SP | CH | PA | OTPersDriverInfo/GoodStudentCd, DistantStudentIndPersDriverInfo/FinancialResponsibilityFiling/FilingStatusCdstringPersDriverInfo/AccidentViolationrepeatedAccidentViolationCd and AccidentViolationDt (both required), AccidentViolationDesc, ConvictionDt, AccidentViolationRecordTypeCd, LossPayment/LossAmt/Amt. AccidentViolationDesc must be a violation type from GET /api/reference/violations; the same rule as JSON description.Vehicles
One PersVeh id="V1" per vehicle, up to 4.
@idstringDriverVeh, e.g. V1.VehIdentificationNumberstring(17)conditionalModelYear + Manufacturer + Model. VIN preferred.VehUseCdenumrequiredPleasure | Commute | Business | Farm | ArtisanCoveragerepeatedconditionalBI and PD are required on the first vehicle, which supplies liability; other vehicles may omit coverages.EstimatedAnnualDistance/NumUnits, OdometerReading/NumUnitsintegerAddr[@AddrTypeCd="GaragingAddress"]addressAntiTheftDeviceCdenumActiveAlarm | PassiveAlarm | ActiveDisablingDevice | PassiveDisablingDevice | RecoveryDevice | VinEtching | OnStarPassiveRestraintCdenumAirbagDriver | AirbagDual | AirbagDualSide | ActiveBelts | PassiveBeltsLeasedVehIndbooleanVehBodyTypeCd, RegistrationStateProvCd, AntiLockBrakeCd, DaytimeRunningLightInd, CostNewAmt/Amt, StatedAmt/Amt, PurchaseDtCoverages
Limits: Limit/FormatCurrencyAmt/Amt with LimitAppliesToCd PerPerson + PerAccident (split) or CSL. Deductibles: Deductible/FormatCurrencyAmt/Amt. Rejection: <CoverageDesc>Rejected</CoverageDesc>. Unknown codes are ignored.
BI, PDlimitrequiredUM (UMBI), UIM (UIMBI), UMPDlimitPIP, MEDPM (MED)limitCOMP (OTC), COLLdeductibleRREIM (RENT), TL (TOW), GAPlimitDriver assignment
DriverVehrepeatedPersDriverRef, PersVehRef, UsePct. Percentages per vehicle must total 100.Full example
<?xml version="1.0" encoding="UTF-8"?>
<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
<InsuranceSvcRq>
<RqUID>c7a2e8f1-4b3d-4a91-8e6c-1f9d3b7a5c02</RqUID>
<com.quomation_LeadSource>Web</com.quomation_LeadSource>
<PersAutoPolicyQuoteInqRq>
<Producer>
<GeneralPartyInfo>
<NameInfo><CommlName><CommercialName>Northwind Insurance</CommercialName></CommlName></NameInfo>
</GeneralPartyInfo>
</Producer>
<InsuredOrPrincipal>
<GeneralPartyInfo>
<NameInfo><PersonName><GivenName>Ryan</GivenName><Surname>Carter</Surname></PersonName></NameInfo>
<Addr AddrTypeCd="InsuredsAddress">
<Addr1>2100 Riverside Dr</Addr1>
<City>Austin</City>
<StateProvCd>TX</StateProvCd>
<PostalCode>78704</PostalCode>
</Addr>
<Communications>
<PhoneInfo><PhoneNumber>512-555-0178</PhoneNumber></PhoneInfo>
<EmailInfo><EmailAddr>r.carter@example.com</EmailAddr></EmailInfo>
</Communications>
</GeneralPartyInfo>
<InsuredOrPrincipalInfo>
<PersonInfo>
<BirthDt>1990-03-22</BirthDt>
<GenderCd>M</GenderCd>
<MaritalStatusCd>M</MaritalStatusCd>
</PersonInfo>
<LengthTimeCurrentAddr><NumUnits>36</NumUnits></LengthTimeCurrentAddr>
</InsuredOrPrincipalInfo>
</InsuredOrPrincipal>
<PersPolicy>
<LOBCd>AUTOP</LOBCd>
<ControllingStateProvCd>TX</ControllingStateProvCd>
<ContractTerm>
<EffectiveDt>2026-10-15</EffectiveDt>
<DurationPeriod><NumUnits>6</NumUnits></DurationPeriod>
</ContractTerm>
<PaymentOption><MethodPaymentCd>PaidInFull</MethodPaymentCd></PaymentOption>
<OtherOrPriorPolicy>
<InsurerName>GEICO</InsurerName>
<ContractTerm><ExpirationDt>2026-10-14</ExpirationDt></ContractTerm>
<LengthTimeWithPreviousInsurer><NumUnits>24</NumUnits></LengthTimeWithPreviousInsurer>
<Coverage>
<CoverageCd>BI</CoverageCd>
<Limit><FormatCurrencyAmt><Amt>30000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerPerson</LimitAppliesToCd></Limit>
<Limit><FormatCurrencyAmt><Amt>60000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerAccident</LimitAppliesToCd></Limit>
</Coverage>
</OtherOrPriorPolicy>
</PersPolicy>
<PersAutoLineBusiness>
<PersDriver id="D1">
<GeneralPartyInfo>
<NameInfo><PersonName><GivenName>Ryan</GivenName><Surname>Carter</Surname></PersonName></NameInfo>
</GeneralPartyInfo>
<DriverInfo>
<PersonInfo>
<BirthDt>1990-03-22</BirthDt>
<GenderCd>M</GenderCd>
<MaritalStatusCd>M</MaritalStatusCd>
</PersonInfo>
<DriversLicense>
<DriversLicenseNumber>41288902</DriversLicenseNumber>
<StateProvCd>TX</StateProvCd>
<LicensedDt>2006-05-02</LicensedDt>
<LicenseStatusCd>Active</LicenseStatusCd>
</DriversLicense>
</DriverInfo>
<PersDriverInfo>
<DriverTypeCd>Rated</DriverTypeCd>
<DriverRelationshipToApplicantCd>IN</DriverRelationshipToApplicantCd>
</PersDriverInfo>
</PersDriver>
<PersDriver id="D2">
<GeneralPartyInfo>
<NameInfo><PersonName><GivenName>Emma</GivenName><Surname>Carter</Surname></PersonName></NameInfo>
</GeneralPartyInfo>
<DriverInfo>
<PersonInfo>
<BirthDt>1991-07-09</BirthDt>
<GenderCd>F</GenderCd>
<MaritalStatusCd>M</MaritalStatusCd>
</PersonInfo>
<DriversLicense>
<DriversLicenseNumber>55122109</DriversLicenseNumber>
<StateProvCd>TX</StateProvCd>
<LicensedDt>2008-08-19</LicensedDt>
<LicenseStatusCd>Active</LicenseStatusCd>
</DriversLicense>
</DriverInfo>
<PersDriverInfo>
<DriverTypeCd>Rated</DriverTypeCd>
<DriverRelationshipToApplicantCd>SP</DriverRelationshipToApplicantCd>
<AccidentViolation>
<AccidentViolationCd>SPD</AccidentViolationCd>
<AccidentViolationDesc>10 - 20 MPH Over Limit</AccidentViolationDesc>
<AccidentViolationDt>2025-06-11</AccidentViolationDt>
</AccidentViolation>
</PersDriverInfo>
</PersDriver>
<PersVeh id="V1">
<VehIdentificationNumber>2T3P1RFV1MC123456</VehIdentificationNumber>
<ModelYear>2021</ModelYear>
<Manufacturer>Toyota</Manufacturer>
<Model>RAV4</Model>
<VehUseCd>Commute</VehUseCd>
<EstimatedAnnualDistance><NumUnits>12000</NumUnits></EstimatedAnnualDistance>
<Coverage>
<CoverageCd>BI</CoverageCd>
<Limit><FormatCurrencyAmt><Amt>30000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerPerson</LimitAppliesToCd></Limit>
<Limit><FormatCurrencyAmt><Amt>60000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerAccident</LimitAppliesToCd></Limit>
</Coverage>
<Coverage>
<CoverageCd>PD</CoverageCd>
<Limit><FormatCurrencyAmt><Amt>25000</Amt></FormatCurrencyAmt></Limit>
</Coverage>
<Coverage>
<CoverageCd>UM</CoverageCd>
<Limit><FormatCurrencyAmt><Amt>30000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerPerson</LimitAppliesToCd></Limit>
<Limit><FormatCurrencyAmt><Amt>60000</Amt></FormatCurrencyAmt><LimitAppliesToCd>PerAccident</LimitAppliesToCd></Limit>
</Coverage>
<Coverage>
<CoverageCd>PIP</CoverageCd>
<Limit><FormatCurrencyAmt><Amt>2500</Amt></FormatCurrencyAmt></Limit>
</Coverage>
<Coverage>
<CoverageCd>COMP</CoverageCd>
<Deductible><FormatCurrencyAmt><Amt>500</Amt></FormatCurrencyAmt></Deductible>
</Coverage>
<Coverage>
<CoverageCd>COLL</CoverageCd>
<Deductible><FormatCurrencyAmt><Amt>500</Amt></FormatCurrencyAmt></Deductible>
</Coverage>
</PersVeh>
<DriverVeh><PersDriverRef>D1</PersDriverRef><PersVehRef>V1</PersVehRef><UsePct>60</UsePct></DriverVeh>
<DriverVeh><PersDriverRef>D2</PersDriverRef><PersVehRef>V1</PersVehRef><UsePct>40</UsePct></DriverVeh>
</PersAutoLineBusiness>
</PersAutoPolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
Errors
Every problem is reported at once. Read the whole list and branch on errorCode, not on the message.
Format
JSON: {"statusCode", "requestId", "errors": [{"errorCode", "errorMessage", "field"}]}. ACORD: one Status per problem after SignonRs; for 401 and 403 the Status also carries com.quomation_ErrorCode with the code below. Token errors use OAuth error / error_description.
Codes
| HTTP | errorCode | What to do |
|---|---|---|
| 400 | QM_FIELD_REQUIRED, QM_FIELD_INVALID, QM_MALFORMED_BODY | Fix every listed field and resubmit. |
| 400 | QM_INVALID_CALLBACK_URL | Fix callbackUrl: https, port 443, a public host. See Callbacks. |
| 401 | QM_UNAUTHORIZED, QM_TOKEN_EXPIRED | Get a new token and retry once. |
| 403 | QM_WRONG_ENVIRONMENT, QM_FORBIDDEN | Wrong host for the credentials, or credentials need reissuing. Do not retry. |
| 404 | QM_JOB_NOT_FOUND | Unknown job id, or another agency's job. |
| 409 | QM_CALLBACK_IN_PROGRESS | The callback is still being delivered. Resend once it is delivered or failed. |
| 413 | QM_PAYLOAD_TOO_LARGE | Body over 2 MB. |
| 422 | QM_NO_ELIGIBLE_CARRIERS | No carrier enabled for this agency in this state. |
| 422 | QM_NO_PRODUCER_ON_FILE | Agency setup problem. Contact us. |
| 422 | QM_NO_CALLBACK | Resend asked for a quote submitted without a callback URL. |
| 500 | QM_INTERNAL_ERROR | Retry with the same idempotency key. |
| 503 | QM_QUEUE_UNAVAILABLE | The quote queue is briefly unavailable. Retry in a minute with the same idempotency key. |
JSON
{
"statusCode": 400,
"requestId": "7f3c2a91-4e88-4d1b-9a02-6b5c1d0e4477",
"errors": [
{ "errorCode": "QM_FIELD_REQUIRED", "errorMessage": "is required.",
"field": "drivers[0].licenseState" },
{ "errorCode": "QM_FIELD_INVALID", "errorMessage": "effectiveDate must be today or a future date.",
"field": "effectiveDate" }
]
}
ACORD
<?xml version="1.0" encoding="utf-8"?>
<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
<SignonRs>
<Status><StatusCd>400</StatusCd><StatusDesc>Request validation failed</StatusDesc></Status>
<ServerDt>2026-10-01T14:32:10Z</ServerDt>
</SignonRs>
<Status>
<StatusCd>400</StatusCd>
<StatusDesc>InsuranceSvcRq/RqUID is required and must be unique per quote attempt.</StatusDesc>
</Status>
<Status>
<StatusCd>400</StatusCd>
<StatusDesc>PersVeh[@id='V1']/Coverage[CoverageCd='BI'] bodily injury liability is required.</StatusDesc>
</Status>
</ACORD>
Limits
| Limit | Value |
|---|---|
| Lines | Personal auto |
| Drivers / vehicles | Up to 4 each |
| Policy term | 6 or 12 months |
| Request body | 2 MB |
| Per carrier | 60 seconds per attempt; up to 3 attempts on connection errors |
| Results | Polling, or a signed callback to your HTTPS URL (port 443) |
| Callback delivery | 10 seconds per attempt; 7 attempts over about 8.7 hours |
| Quotes created | A new quote job is backed by a quote in AgencyStream. Repeating a saved request returns its existing job. Some submission errors, including agency setup and queue errors, can occur after the quote has been created. |