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

  1. Get a token with your client id and secret.
  2. Submit the risk. You get 202 Accepted and a jobId straight away.
  3. 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 needWhere it comes from
Client id and secretIssued by Quomation per agency and environment. See Authentication.
Carriers set up for your agentCarrier logins in the agency's AgencyStream account.
An environmentSandbox for development, production for live quotes. See Environments.

Where to go next

PageWhat it covers
QuickstartThe shortest path from credentials to prices.
Getting the most carriersWhat to send so every carrier that can quote, does.
Quote lifecycleJob statuses, carrier outcomes, when to stop polling.
JSON request bodyEvery field, with what is required.

Environments

Two environments on separate hosts, with separate credentials.

EnvironmentBase URLReaches
Sandboxhttps://testapi.agencystream.comEach carrier's own test system.
Productionhttps://api.agencystream.comLive 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

HeaderMeaning
X-Quomation-Environmentsandbox or production. A call to the wrong host is obvious at once.
X-Quomation-Request-IdIdentifies 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

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

PageWhy
Getting the most carriersTurn declines and failures into quotes.
JSON request bodyEvery field you can send.
CallbacksHave results POSTed to you instead of polling.
ErrorsWhat 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

Coverages

Applicant and drivers

Vehicles

Polling and failures

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

JSONACORDMeaning
pendingPendingAccepted, no carrier started yet. Keep polling.
inProgressInProgressCarriers are answering. Keep polling.
completedCompletedEvery carrier has a final answer. Stop.
partiallyCompletedPartiallyCompletedWe stopped waiting; carriers without an answer are marked failed. Stop.

Carrier status

JSONACORDMeaning
pending, runningStatusCd Pending / RunningNot finished yet.
quotedStatusCd 0, MsgStatusCd SuccessAt least one priced plan.
bridgeStatusCd 0, com.quomation_BridgeURLNo price through the API; finish in the carrier portal.
declinedStatusCd 0, MsgStatusCd RejectedThe carrier refused the risk. Retrying will not help.
failedStatusCd 502 / 504No answer: timeout, carrier error or a carrier login problem.
notAvailableStatusCd 403Carrier 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.

LevelJSONACORD
Carrierone entry in carriers[]one InsuranceSvcRs, keyed by SPName
Planplans[]one PersAutoPolicyQuoteInqRs per plan
Way to paypaymentOptions[]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.

FormatKey
JSONIdempotency-Key header, any unique string up to 100 characters.
ACORDInsuranceSvcRq/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

WhereApplies to
?callbackUrl=https://… on POST /api/quotes/auto or POST /api/acord/autoThis quote only. Wins over the default.
Default callback URL of your API client, set by Quomation on requestEvery quote submitted without callbackUrl.
NeitherNo 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.

HeaderValue
X-Quomation-Eventquote.job.completed (ping for a test).
X-Quomation-Event-Idevt_ + the job id. The same on every attempt — use it to drop duplicates.
X-Quomation-Job-IdThe job id.
X-Quomation-Environmentsandbox or production.
X-Quomation-Delivery-Attempt1, 2, …
X-Quomation-TimestampUnix time (seconds) this attempt was sent.
X-Quomation-Signaturev1= + 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:

  1. Take X-Quomation-Timestamp, a dot and the raw request body, before any parsing.
  2. Compute HMAC-SHA256 over it with the secret (as UTF-8 text) as the key, hex-encode it and put v1= in front.
  3. Compare with X-Quomation-Signature in 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 answerWhat happens
2xxDelivered. Done.
408, 429, 5xx, timeout, connection or TLS errorRetried.
3xx, any other 4xxNot 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.

JSONACORDMeaning
pendingPendingThe job is still running.
queuedQueuedThe job finished; the callback is being sent.
retryingRetryingAn attempt failed; another one is scheduled.
deliveredDeliveredYour server answered 2xx.
failedFailedGiven 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

POSThttps://testapi.agencystream.com/api/auth

Exchanges client credentials for a bearer token. Errors use the OAuth format (error, error_description).

Request

Authorizationheaderrequired
Basic base64(client_id:client_secret). Credentials in the form body are also accepted.
Content-Typeheaderrequired
application/x-www-form-urlencoded
grant_typeformrequired
Always client_credentials.

Response

access_tokenstring
Send as Authorization: Bearer ....
token_typestring
Always Bearer.
expires_ininteger
Seconds until expiry.
environmentstring
sandbox or production.
Responses
200Token issued.
400unsupported_grant_type: grant type is not client_credentials.
401invalid_client: unknown, wrong, revoked or expired credentials.
403Credentials belong to the other environment.
500server_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)

POSThttps://testapi.agencystream.com/api/quotes/auto

Validates 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

Authorizationheaderrequired
Bearer <token>
Content-Typeheaderrequired
application/json
Idempotency-Keyheader
Unique per quote attempt. A repeat returns the original job. See Idempotency.
callbackUrlquery, url
Where to POST the result when the job finishes. Overrides your default. See Callbacks.
bodyobjectrequired
The risk. See JSON request body.

Response

jobIduuid
Poll this job for results.
totalCarriersinteger
How many carriers the job will report on.
statusUrlurl
Ready-made polling URL.
requestIdstring
Same as X-Quomation-Request-Id.
environmentstring
sandbox or production.
submittedAtdatetime
UTC.
callbackUrlurl
Where the result will be POSTed. Absent when the quote has no callback.
Responses
202Accepted. Poll the job.
400Validation failed. Every problem is listed in errors[], including QM_INVALID_CALLBACK_URL for a rejected callbackUrl.
401Missing or expired token.
403Wrong environment, or credentials need reissuing.
413Body over 2 MB.
422No carrier enabled for this state, or agency setup problem.
500Our fault. Retry with the same idempotency key.
503QM_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)

GEThttps://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, uuidrequired
From the submit response.
Authorizationheaderrequired
Bearer <token>. Only the agency that created the job can read it.

Response

statusstring
pending | inProgress | completed | partiallyCompleted.
totalCarriersinteger
Number of entries in carriers[].
summaryobject
carriersQueried, carriersQuoted, plansReturned, declined, failed, bridgeOnly, lowestPremium, lowestPremiumCarrier, elapsedMs.
carriers[].carrierstring
Carrier name.
carriers[].statusstring
carriers[].reasonstring
Why a carrier declined, failed or is not available.
carriers[].bridgeUrlurl
Carrier portal link, where offered.
carriers[].carrierRequestId, elapsedMs, messages[]
Our id for this carrier call (quote it with the request id in support tickets), how long the carrier took, and messages the carrier returned.
carriers[].plans[]array
name, fullTermPremium, term, quoteNumber, ratedLimits, paymentOptions[], messages[].
…paymentOptions[]array
method (paidInFull | installments), downPayment, installmentAmount, installmentCount, totalPayable.
callbackobject
Only for a quote with a callback: url, status, attempts, lastAttemptAt, lastHttpStatus, lastError, deliveredAt. See Delivery status.
Responses
200Current state of the job.
401Missing or expired token.
404No such job for your agency.

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)

POSThttps://testapi.agencystream.com/api/acord/auto

The same as the JSON submit, with an ACORD P&C PersAutoPolicyQuoteInqRq document. Poll GET /api/acord/quotes/{jobId} for results.

Request

Authorizationheaderrequired
Bearer <token>
Content-Typeheaderrequired
application/xml, UTF-8.
bodyACORD XMLrequired
See ACORD request. InsuranceSvcRq/RqUID is the idempotency key.
callbackUrlquery, url
Where to POST the result when the job finishes. Overrides your default. See Callbacks.

Response

An ACORD document: SignonRs/Status/StatusCd 0, plus com.quomation_JobId and com.quomation_TotalCarriers. Errors put one Status per problem after SignonRs.

Responses
202Accepted. Poll the job.
400Not well-formed or validation failed; every problem listed. A rejected callbackUrl is reported against com.quomation_CallbackUrl.
401Missing or expired token.
403Wrong environment, or credentials need reissuing.
413Body over 2 MB.
422No carrier enabled for this state, or agency setup problem.
500Our fault. Retry with the same RqUID.
503QM_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)

GEThttps://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_JobStatusstring
Pending | InProgress | Completed | PartiallyCompleted.
com.quomation_TotalCarriersinteger
Number of InsuranceSvcRs blocks.
InsuranceSvcRs/Status/StatusCdstring
0 answered; Pending/Running not yet; 403, 502, 504 no answer.
InsuranceSvcRs/SPNamestring
The carrier.
…/MsgStatus/MsgStatusCdstring
Success, SuccessWithInfo or Rejected (declined).
…/MsgStatus/ExtendedStatusrepeated
Carrier messages.
…/CompanyProductCdstring
Plan name.
…/PolicySummaryInfo/FullTermAmt/Amtdecimal
Full-term premium.
…/PolicySummaryInfo/PolicyNumberstring
Carrier quote number.
…/PaymentOptionrepeated
MethodPaymentCd, DownPaymentAmt, InstallmentInfo, com.quomation_TotalPayableAmt.
…/com.quomation_BridgeURLurl
Carrier portal link.
com.quomation_Callbackelement
Only for a quote with a callback: Url, StatusCd, Attempts, LastAttemptDt, LastHttpStatus, LastError, DeliveredDt. See Delivery status.
Responses
200Current state of the job.
401Missing or expired token.
404No such job for your agency.

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

GEThttps://testapi.agencystream.com/api/callbacks/signing-secret

The secret your callbacks are signed with. Each API client has its own, separate from its client secret.

Request

Authorizationheaderrequired
Bearer <token>

Response

clientIdstring
Your client id.
versioninteger
Goes up by one on every rotation.
secretstring
The signing secret, qcs_…. Use it as the HMAC key exactly as given. Keep it on your server.
algorithm, signedPayload, signatureHeaderstring
How the signature is built, for reference.
defaultCallbackUrlurl
Your default callback URL, or null.
Responses
200The current secret.
401Missing or expired token.

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

POSThttps://testapi.agencystream.com/api/callbacks/signing-secret/rotate

Replaces 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

Authorizationheaderrequired
Bearer <token>

Response

Responses
200The new secret; version is one higher.
401Missing or expired token.

Request

curl -X POST https://testapi.agencystream.com/api/callbacks/signing-secret/rotate 
  -H "Authorization: Bearer $TOKEN"

Send a test callback

POSThttps://testapi.agencystream.com/api/callbacks/test

Sends 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

Authorizationheaderrequired
Bearer <token>
callbackUrlquery, url
Where to send the ping. Without it, your default callback URL.

Response

callbackUrlurl
Where the ping went.
eventIdstring
evt_test_…, also sent as X-Quomation-Event-Id.
deliveredboolean
true when your endpoint answered 2xx.
httpStatusinteger
Your endpoint’s status code; null when it did not answer.
elapsedMsinteger
How long the call took.
errorstring
What went wrong, when it did.
wouldRetryboolean
Whether a real callback would be retried after this answer.
Responses
200The test ran. Read delivered: a failing endpoint is still a 200 here.
400QM_INVALID_CALLBACK_URL: bad callbackUrl, or none given and no default set.
401Missing or expired token.

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

POSThttps://testapi.agencystream.com/api/callbacks/jobs/{jobId}/resend

Delivers 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, uuidrequired
From the submit response.
Authorizationheaderrequired
Bearer <token>

Response

Responses
202Queued. Watch callback.status on GET.
401Missing or expired token.
404No such job for your agency.
409QM_CALLBACK_IN_PROGRESS: still being delivered. Wait until it is delivered or failed.
422QM_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

GEThttps://testapi.agencystream.com/api/reference/occupations

The 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

GEThttps://testapi.agencystream.com/api/reference/violations

The 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

effectiveDatedaterequired
Today or later.
statestring(2)required
Rating state. Must equal applicant.address.state.
termMonthsinteger
6 (default) or 12.
paymentMethodenum
paidInFull | installments | eft. Changes the premium on most carriers.
carriersstring[]
Carrier names. Omit to quote all (recommended).
leadSourcestring
Lead source shown to the agency.
producer.agencyNamestring
Your agency name.
producer.producerCode, phone, address
Producer code with the carrier; agency phone and address.

Applicant

applicant.firstName, lastNamestringrequired
applicant.middleName, suffixstring
applicant.dateOfBirthdaterequired
applicant.genderenumrequired
male | female
applicant.maritalStatusenumrequired
single | married | divorced | widowed | separated | domesticPartner | domesticPartnerUnregistered
applicant.address.line1, city, state, zipstringrequired
Residence; drives territory. ZIP+4 is trimmed to five digits.
applicant.address.countystring
applicant.phone, emailstring
Recommended. 10-digit US phone.
applicant.industry, occupationstring
A pair from GET /api/reference/occupations; industry is required with occupation. Any other text is rejected: carriers only recognise these titles.
applicant.educationenum
none | highSchool | vocational | someCollege | communityCollege | associates | bachelors | masters | law | medical | doctorate
applicant.monthsAtAddressinteger

Prior 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.carrierNamestringrequired
Required inside the object.
priorInsurance.expirationDatedate
Lapse is computed from it.
priorInsurance.monthsWithCarrierinteger
priorInsurance.bodilyInjuryLimitstring
e.g. 30000/60000.
priorInsurance.naicCode, policyNumberstring
priorInsurance.effectiveDatedate
Start of the prior term.

Drivers

1 to 4 drivers.

drivers[].idstring
Your id, used by driverAssignments, e.g. d1.
drivers[].firstName, lastNamestring
Send them; carriers need a name per driver.
drivers[].middleName, suffixstring
drivers[].dateOfBirthdaterequired
drivers[].gender, maritalStatusenumrequired
As for the applicant.
drivers[].statusenumrequired
rated | excluded | listed. listed: a household member reported to carriers but not rated.
drivers[].relationshipenum
insured | spouse | child | parent | other
drivers[].licenseStatestring(2)required
drivers[].licenseNumberstring
Strongly recommended.
drivers[].licensePermitNumberstring
Learner’s permit, when there is no licence number yet.
drivers[].licensedDatedate
Without it carriers assume licensed at 16.
drivers[].licenseStatusenum
valid (default) | suspended | revoked | expired | permit
drivers[].industry, occupation, education
As for the applicant. Omitted, carriers get AgencyStream's defaults (Business/Sales/Office, Director/Administrator, High School Diploma).
drivers[].goodStudent, studentAwayAtSchoolboolean
Student discounts.
drivers[].requiresFinancialFilingboolean
SR-22 / FR-44.
drivers[].violations[]array
code (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)conditional
Or year + make + model. VIN preferred.
vehicles[].useenumrequired
pleasure | commute | business | farm | artisan
vehicles[].coveragesobjectconditional
See below. Required on the first vehicle, which supplies liability; other vehicles may omit it.
vehicles[].idstring
Used by driverAssignments, e.g. v1.
vehicles[].annualMileage, odometerinteger
Recommended.
vehicles[].garagingAddressobject
Only if different from the applicant address.
vehicles[].leasedboolean
Needed for loan/lease gap.
vehicles[].antiTheftDeviceenum
activeAlarm | passiveAlarm | activeDisablingDevice | passiveDisablingDevice | recoveryDevice | vinEtching | onStar
vehicles[].passiveRestraintenum
airbagDriver | airbagDual | airbagDualSide | activeBelts | passiveBelts
vehicles[].bodyStyle, registrationState, antiLockBrakes, daytimeRunningLights, costNew, statedValue, purchaseDate
Optional.

Coverages

Limits in dollars. Split: {"perPerson": 30000, "perAccident": 60000}. Single or CSL: {"limit": 25000}. Deductible: {"deductible": 500}. Rejection: {"rejected": true}.

bodilyInjurylimitrequired
propertyDamagelimitrequired
uninsuredMotorist, underinsuredMotorist, uninsuredMotoristPropertyDamagelimit
Send, or reject where allowed.
personalInjuryProtection, medicalPaymentslimit
Per state rules; PIP is mandatory in no-fault states.
comprehensive, collisiondeductible
rentalReimbursement, towinglimit
loanLeaseGapboolean
Needs leased: true.

Driver assignments

driverAssignments[]array
driverId, 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/RqUIDuuidrequired
New per quote attempt; the idempotency key. JSON: Idempotency-Key header.
InsuranceSvcRq/com.quomation_CarrierSelection/com.quomation_Carrierstring, repeated
Carrier names. Omit to quote all (recommended). JSON: carriers.
InsuranceSvcRq/com.quomation_LeadSourcestring
Lead source shown to the agency. JSON: leadSource.
…/Producer/GeneralPartyInfo/NameInfo/CommlName/CommercialNamestring
Your agency name. JSON: producer.agencyName.
…/Producer/ProducerInfo/ContractNumberstring
Producer code with the carrier. JSON: producer.producerCode.
…/Producer/GeneralPartyInfo/Addr, Communications/PhoneInfo/PhoneNumberaddress, string
Agency address and phone. JSON: producer.address, producer.phone.

Applicant

Under PersAutoPolicyQuoteInqRq/InsuredOrPrincipal.

GeneralPartyInfo/NameInfo/PersonName/GivenName, Surnamestringrequired
JSON: applicant.firstName, lastName.
GeneralPartyInfo/NameInfo/PersonName/OtherGivenName, NameSuffixstring
Middle name, suffix. JSON: middleName, suffix.
GeneralPartyInfo/Addr[@AddrTypeCd="InsuredsAddress"]/Addr1, City, StateProvCd, PostalCodestringrequired
Residence; drives territory. ZIP+4 is trimmed to five digits.
GeneralPartyInfo/Addr[@AddrTypeCd="InsuredsAddress"]/Countystring
GeneralPartyInfo/Communications/PhoneInfo/PhoneNumber, EmailInfo/EmailAddrstring
Recommended. 10-digit US phone.
InsuredOrPrincipalInfo/PersonInfo/BirthDtdaterequired
InsuredOrPrincipalInfo/PersonInfo/GenderCdenumrequired
M | F
InsuredOrPrincipalInfo/PersonInfo/MaritalStatusCdenumrequired
S | M | D | W | P | DomesticPartner | DomesticPartnerUnregistered
InsuredOrPrincipalInfo/PersonInfo/com.quomation_Industry, OccupationDescstring
A pair from GET /api/reference/occupations; industry is required with the occupation.
InsuredOrPrincipalInfo/PersonInfo/EducationLevelCdenum
None | HighSchool | Vocational | SomeCollege | CommunityCollege | Associates | Bachelors | Masters | Law | Medical | Doctorate
InsuredOrPrincipalInfo/LengthTimeCurrentAddr/NumUnitsinteger
Months at the address.

Policy and prior insurance

Under PersAutoPolicyQuoteInqRq/PersPolicy.

LOBCdenum
AUTOP
ControllingStateProvCdstring(2)required
Rating state. Must equal the applicant state.
ContractTerm/EffectiveDtdaterequired
Today or later.
ContractTerm/DurationPeriod/NumUnitsinteger
6 (default) or 12.
PaymentOption/MethodPaymentCdenum
PaidInFull | Installments | EFT. Changes the premium on most carriers.
OtherOrPriorPolicy/InsurerNamestringconditional
Required when the block is sent.
OtherOrPriorPolicy/ContractTerm/ExpirationDtdate
Lapse is computed from it.
OtherOrPriorPolicy/ContractTerm/EffectiveDtdate
Start of the prior term.
OtherOrPriorPolicy/LengthTimeWithPreviousInsurer/NumUnitsinteger
Months with that carrier.
OtherOrPriorPolicy/Coverage[CoverageCd="BI"]limit
Prior bodily injury limit, in dollars.
OtherOrPriorPolicy/NAICCd, PolicyNumberstring
com.quomation_NoPriorInsuranceReasonenum
Instead of OtherOrPriorPolicy when there is no prior policy: DidNotOwnVehicle | MilitaryService | VehicleInStorage | LapsedUnder30Days | LapsedOver30Days.

Drivers

One PersAutoLineBusiness/PersDriver id="D1" per driver, up to 4.

@idstring
Your id, used by DriverVeh, e.g. D1.
GeneralPartyInfo/NameInfo/PersonName/GivenName, Surnamestring
Directly inside PersDriver. Send them; carriers need a name per driver.
GeneralPartyInfo/NameInfo/PersonName/OtherGivenName, NameSuffixstring
Middle name, suffix.
DriverInfo/PersonInfo/BirthDtdaterequired
DriverInfo/PersonInfo/GenderCd, MaritalStatusCdenumrequired
As for the applicant.
DriverInfo/PersonInfo/com.quomation_Industry, OccupationDesc, EducationLevelCd
As for the applicant.
DriverInfo/DriversLicense/StateProvCdstring(2)required
DriverInfo/DriversLicense/DriversLicenseNumberstring
Strongly recommended.
DriverInfo/DriversLicense/LicensePermitNumberstring
Learner’s permit, when there is no licence number yet.
DriverInfo/DriversLicense/LicensedDtdate
Without it carriers assume licensed at 16.
DriverInfo/DriversLicense/LicenseStatusCdenum
Active | Suspended | Revoked | Expired | Permit
PersDriverInfo/DriverTypeCdenumrequired
Rated | Excluded | Listed. Listed: reported to carriers but not rated.
PersDriverInfo/DriverRelationshipToApplicantCdenum
IN | SP | CH | PA | OT
PersDriverInfo/GoodStudentCd, DistantStudentInd
Student discounts.
PersDriverInfo/FinancialResponsibilityFiling/FilingStatusCdstring
SR-22 / FR-44.
PersDriverInfo/AccidentViolationrepeated
AccidentViolationCd 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.

@idstring
Your id, used by DriverVeh, e.g. V1.
VehIdentificationNumberstring(17)conditional
Or ModelYear + Manufacturer + Model. VIN preferred.
VehUseCdenumrequired
Pleasure | Commute | Business | Farm | Artisan
Coveragerepeatedconditional
See below. BI and PD are required on the first vehicle, which supplies liability; other vehicles may omit coverages.
EstimatedAnnualDistance/NumUnits, OdometerReading/NumUnitsinteger
Recommended.
Addr[@AddrTypeCd="GaragingAddress"]address
Only if different from the applicant address.
AntiTheftDeviceCdenum
ActiveAlarm | PassiveAlarm | ActiveDisablingDevice | PassiveDisablingDevice | RecoveryDevice | VinEtching | OnStar
PassiveRestraintCdenum
AirbagDriver | AirbagDual | AirbagDualSide | ActiveBelts | PassiveBelts
LeasedVehIndboolean
Needed for loan/lease gap.
VehBodyTypeCd, RegistrationStateProvCd, AntiLockBrakeCd, DaytimeRunningLightInd, CostNewAmt/Amt, StatedAmt/Amt, PurchaseDt
Optional.

Coverages

Limits: Limit/FormatCurrencyAmt/Amt with LimitAppliesToCd PerPerson + PerAccident (split) or CSL. Deductibles: Deductible/FormatCurrencyAmt/Amt. Rejection: <CoverageDesc>Rejected</CoverageDesc>. Unknown codes are ignored.

BI, PDlimitrequired
On the first vehicle, which also supplies MEDPM and PIP.
UM (UMBI), UIM (UIMBI), UMPDlimit
PIP, MEDPM (MED)limit
COMP (OTC), COLLdeductible
RREIM (RENT), TL (TOW), GAPlimit

Driver assignment

DriverVehrepeated
PersDriverRef, 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

HTTPerrorCodeWhat to do
400QM_FIELD_REQUIRED, QM_FIELD_INVALID, QM_MALFORMED_BODYFix every listed field and resubmit.
400QM_INVALID_CALLBACK_URLFix callbackUrl: https, port 443, a public host. See Callbacks.
401QM_UNAUTHORIZED, QM_TOKEN_EXPIREDGet a new token and retry once.
403QM_WRONG_ENVIRONMENT, QM_FORBIDDENWrong host for the credentials, or credentials need reissuing. Do not retry.
404QM_JOB_NOT_FOUNDUnknown job id, or another agency's job.
409QM_CALLBACK_IN_PROGRESSThe callback is still being delivered. Resend once it is delivered or failed.
413QM_PAYLOAD_TOO_LARGEBody over 2 MB.
422QM_NO_ELIGIBLE_CARRIERSNo carrier enabled for this agency in this state.
422QM_NO_PRODUCER_ON_FILEAgency setup problem. Contact us.
422QM_NO_CALLBACKResend asked for a quote submitted without a callback URL.
500QM_INTERNAL_ERRORRetry with the same idempotency key.
503QM_QUEUE_UNAVAILABLEThe 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

LimitValue
LinesPersonal auto
Drivers / vehiclesUp to 4 each
Policy term6 or 12 months
Request body2 MB
Per carrier60 seconds per attempt; up to 3 attempts on connection errors
ResultsPolling, or a signed callback to your HTTPS URL (port 443)
Callback delivery10 seconds per attempt; 7 attempts over about 8.7 hours
Quotes createdA 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.