Payments
New Sales API
Submit vehicle and home warranty sales with their payment plans, validated and booked in real time over HTTPS.
Introduction
The ArchPay New Sales API lets an authorized partner organization submit vehicle and home warranty sales (service contracts) with their payment plans, programmatically, over HTTPS.
Business objective. Replace manual / batch sale entry with a validated, real-time API so partner organizations can submit a sale, have it checked against Arch's business rules immediately, collect any due-now payment, and book the resulting contract. The API is the single front door for new-sale intake.
What the API does
- Authenticates the calling organization with OAuth 2.0 client credentials.
- Accepts a single sale (
POST /v1/sales) or up to 100 sales in one call (POST /v1/sales/batch). - Validates each sale in stages: request format and cross-field structure, then identity and duplicate checks, then business math and date rules.
- For payment plans that move money at sale time, charges the card through Arch's payment processor before the sale is booked.
- Returns a precise, coded result for every sale, including warnings that do not block booking.
API Overview
| Architecture style | REST over HTTPS. JSON request and response bodies (the token endpoint uses form-encoded input per the OAuth 2.0 spec). |
|---|---|
| Base URL (Sandbox) | https://api-sandbox.archcrm.com — the partner test environment. Traffic passes through Azure API Management: every request must also carry the Ocp-Apim-Subscription-Key header issued during onboarding (a request without it is rejected 401 before reaching the API). |
| Base URL (Production) | https://api.archcrm.com — same gateway rule as sandbox, with a separate production subscription key issued during onboarding. |
| Versioning | URI path prefix /v1 on all functional endpoints. |
| Authentication | OAuth 2.0 client_credentials grant; short-lived JWT bearer tokens. In sandbox and production, the gateway subscription key header on every request as well. |
| Transport security | HTTPS only (HTTP is rejected). Minimum TLS 1.2. HTTP/2 enabled. |
| Rate limiting | Sandbox and production: 120 requests per 60 seconds per organization (per subscription key), enforced at the API gateway. Exceeding it returns HTTP 429 with a gateway-generated body — back off until the window resets. Limits may be tuned per partner tier. |
| Content type | Requests: application/json (sales) or application/x-www-form-urlencoded (token). Responses: application/json. |
| Request size limit | 1 MB per request body. A larger request is rejected with HTTP 413 without being read. |
| Character encoding | UTF-8 on the wire. Field values are restricted to standard Latin characters by the field constraints. |
Endpoint summary
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /v1/oauth/token | Client ID + secret | Obtain a bearer access token. |
| POST | /v1/sales | Bearer | Submit one sale. |
| POST | /v1/sales/batch | Bearer | Submit up to 100 sales (partial success). |
| GET | /health | None | Liveness/readiness probe (HEAD is also accepted, for uptime monitors). |
Authentication & Authorization
/health
— must carry both:
- The gateway subscription key, in the
Ocp-Apim-Subscription-Keyheader. Checked by the gateway before the request reaches the API. - The OAuth bearer token, in the
Authorizationheader (all/v1/sales*calls). Checked by the API itself.
Mechanism
The API uses the OAuth 2.0 client credentials grant. Your organization is issued a
client_id and client_secret. You exchange them at the token endpoint for a
short-lived JWT access token, then send that token as a bearer credential on every sales request.
Obtaining a token
Send a form-encoded POST to /v1/oauth/token:
| Parameter | In | Required | Value |
|---|---|---|---|
Ocp-Apim-Subscription-Key | header | Yes | Your gateway subscription key for this environment. See Gateway Subscription Key. |
grant_type | form body | Yes | Must be client_credentials. |
client_id | form body | Yes | Your organization's client identifier (opaque string). |
client_secret | form body | Yes | Your organization's client secret. |
Example request
curl -X POST https://api-sandbox.archcrm.com/v1/oauth/token \
-H "Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Success response (HTTP 200)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 1800
}
| access_token | JWT signed with HS256. Treat it as an opaque string; do not parse or depend on its claims. |
|---|---|
| token_type | Always Bearer. |
| expires_in | Token lifetime in seconds. Currently 1800 (30 minutes). |
Using the token
Send the token in the Authorization header on every call to /v1/sales and /v1/sales/batch:
Authorization: Bearer {access_token}
In sandbox and production, every request additionally carries the
Ocp-Apim-Subscription-Key header issued during onboarding, one key per
environment (see API Overview).
A missing, malformed, expired, or otherwise invalid token is answered with HTTP 401,
header WWW-Authenticate: Bearer, and the body
{"status":"rejected","requestId":"...","error":"invalid_token"}.
Token lifecycle
- Lifetime: 30 minutes. There is no refresh token in the client-credentials grant; request a new token when the current one nears expiry.
- Caching: reuse a token until it is close to expiring rather than minting one per request.
- Revocation: tokens are not individually revocable and there is no introspection endpoint. Deactivating a client stops new tokens from being issued; an already-issued token remains valid until it expires. The short lifetime bounds exposure.
Authorization model (organization → producers)
Your token represents your organization (an Arch tenant), not an individual producer.
An organization owns one or more producer codes. Every sale names a producerCode,
and the API verifies that the named producer is authorized for your organization. A sale that uses a
producer code not mapped to your organization is rejected with
50009 PRODUCER_NOT_AUTHORIZED.
Security considerations
- Store the
client_secretin a secret manager; never embed it in client-side code or commit it to source control. - All traffic must use HTTPS (TLS 1.2+). Plain HTTP is refused.
- Secrets are stored by Arch as salted PBKDF2-SHA256 hashes; they cannot be recovered, only reset.
- Card data is passed through to the payment processor and tokenized (vaulted); ArchPay does not store raw PANs. CVV is not collected.
- Treat the gateway subscription key like a secret too: keep it server-side, never in browser or mobile code.
Gateway Subscription Key
Sandbox and production traffic enters through Azure API Management (the API gateway). The gateway admits only requests that carry a valid subscription key.
| Header | Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY |
|---|---|
| Required on | Every request: POST /v1/oauth/token, POST /v1/sales, POST /v1/sales/batch, and GET/HEAD /health. |
| Issued by | The Arch team during onboarding, together with your OAuth client credentials. There is no self-service flow. |
| Per environment | One key for sandbox, a different key for production. A sandbox key does not work against production. |
| Primary / secondary | Each subscription has two keys; either one is accepted. Use the second key to rotate without downtime: switch your systems to it, then ask Arch to regenerate the first. |
| What it does | Admits and meters your traffic (120 requests per 60 seconds, 1 MB body). It does not identify your organization for authorization — that is the OAuth token's job. |
Gateway responses
These come from the gateway, not the API, so their body shape differs from the API's
{"status":"rejected",...} envelope and they carry no requestId.
| Status | When | Example body |
|---|---|---|
| 401 | The Ocp-Apim-Subscription-Key header is missing. | {"statusCode":401,"message":"Access denied due to missing subscription key. ..."} |
| 401 | The key is wrong, regenerated, for the other environment, or the subscription is suspended. | {"statusCode":401,"message":"Access denied due to invalid subscription key. ..."} |
| 429 | More than 120 requests in 60 seconds on this key. | {"statusCode":429,"message":"Rate limit is exceeded. Try again in N seconds."} |
statusCode
in the body is the gateway (check the subscription key). A 401 with "error":"invalid_token" and
a WWW-Authenticate: Bearer header is the API (get a new bearer token).POST /v1/oauth/token
Exchanges client credentials for a bearer access token. See Authentication for the parameter table and a request/response example.
Responses
| Status | Body | When |
|---|---|---|
| 200 | {access_token, token_type, expires_in} | Credentials valid. |
| 401 | {"statusCode":401,"message":"..."} | Gateway: subscription key missing or invalid. See Gateway Subscription Key. |
| 400 | {"error":"unsupported_grant_type"} | grant_type is not client_credentials. |
| 401 | {"error":"invalid_client"} | Unknown client, inactive client, or wrong secret. |
| 422 | {"status":"rejected","requestId":"...","errors":[{"code":50012,...}]} | A required form field is missing or the body is malformed. |
| 503 | {"error":"temporarily_unavailable"} | The credential store is unreachable; no token was issued. Retry later. |
{"error":...} object for credential and availability problems, and the coded
{"status":"rejected",...} envelope for a missing form field.POST /v1/sales
Purpose. Submit a single sale for validation, payment (if due now), and booking.
Request
| Where | Name | Required | Notes |
|---|---|---|---|
| Header | Ocp-Apim-Subscription-Key | Yes | Gateway subscription key. See Gateway Subscription Key. |
| Header | Authorization | Yes | Bearer {access_token} |
| Header | Content-Type | Yes | application/json |
| Body | Sale object | Yes | A single sale. Full schema in Data Models. |
Example request (Auto, financed / NonMTM, paid by card)
curl -X POST https://api-sandbox.archcrm.com/v1/sales \
-H "Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"sale": {
"saleType": 2,
"contractNumber": "H612718768",
"producerCode": 3046,
"administratorCode": 1002,
"insuranceCarrierCode": 2010,
"saleDate": "2026-06-10"
},
"coverage": {
"termMonths": 60,
"effectiveDate": "2026-06-15",
"expirationDate": "2031-06-15",
"administratorCost": 955.00
},
"vehicle": {
"vin": "1HGCM82633A004352",
"year": 2019, "make": "Honda", "model": "Accord",
"termMiles": 36000, "saleOdometer": 42500,
"effectiveOdometer": 42500, "expiryOdometer": 78500
},
"primaryContractHolder": {
"firstName": "Jane", "lastName": "Doe",
"phone": "3125551212", "email": "jane@example.com", "preferredLanguage": 1
},
"addresses": {
"mailing": {"line1":"33 W Delaware Pl","line2":"Apt 6A","city":"Chicago","state":"IL","zipCode":"60610"},
"billing": {"line1":"33 W Delaware Pl","city":"Chicago","state":"IL","zipCode":"60610"}
},
"paymentPlan": {
"retailCost": 4000.00, "downPayment": 200.00,
"downPaymentProcessMethod": 1, "startingBalance": 3800.00,
"installmentStartDate": "2026-07-01", "billingCycle": 1,
"numberOfInstallments": 24, "installmentAmount": 158.33,
"paymentMethod": 1
},
"paymentInstrument": {
"card": {"holderName":"Jane Doe","number":"4111111111111111","expiration":"08/2028"}
}
}'
Success response
201 Created A sale that passes every check returns:
{
"status": "accepted",
"requestId": "a1b2c3d4e5f6a7b8",
"contractId": 90432,
"contractNumber": "H612718768",
"warnings": []
}
contractId is Arch's internal contract identifier; contractNumber is your key
echoed back; requestId is an Arch-generated correlation id (present on every response).
No payment / charge-receipt object is returned: when a due-now (FinCo) card charge is
taken, it is reconciled out of band — the success envelope carries exactly the five fields above.
Rejection responses
| Status | Body shape | When |
|---|---|---|
| 422 | {"status":"rejected","requestId":"...","contractNumber":"...","errors":[{code,field,message}],"warnings":[...]} | One or more validation rules failed (format, identity/duplicate, business/pricing, or payment input), the card charge was declined (60302), or a due-now charge is owed but the producer has no processor / processor key configured (60306, 60307). |
| 401 | {"status":"rejected","requestId":"...","error":"invalid_token"} | Missing, malformed, expired, or invalid bearer token. Returns header WWW-Authenticate: Bearer. |
| 401 | {"statusCode":401,"message":"..."} | Gateway: subscription key missing or invalid. See Gateway Subscription Key. |
| 429 | {"statusCode":429,"message":"..."} | Gateway: rate limit exceeded. Back off until the window resets. |
| 413 | {"status":"rejected","requestId":"...","error":"request body too large"} | The request body exceeds the 1 MB cap. Nothing was processed. |
| 500 | {"status":"rejected","requestId":"...","errors":[{"code":60303|60304|60305,...}]} | Booking failed. 60303: the charge was reversed, safe to resubmit. 60304: the reversal also failed — do not blindly resubmit; contact Arch. 60305: nothing was charged, safe to resubmit. |
| 502 | {"status":"rejected","requestId":"...","errors":[{"code":60301,...}]} | The payment processor was unavailable; the sale was not booked. Safe to retry later. |
| 503 | {"status":"error","requestId":"...","message":"sale validation is unavailable; the sale was not processed"} | A required backend dependency is down. Retry later. |
Example rejection (HTTP 422)
{
"status": "rejected",
"requestId": "a1b2c3d4e5f6a7b8",
"contractNumber": "H612718768",
"errors": [
{"code": 50117, "field": "addresses.mailing.zipCode", "message": "must be 5 digits"},
{"code": 50207, "field": "paymentPlan.startingBalance", "message": "must equal retailCost minus downPayment"}
],
"warnings": []
}
contractNumber is echoed back on rejections too (normalized); it is
null when the body was unreadable.
All independent violations found in one pass are returned together so they can be fixed in a single round trip. See the Error Catalog for validation ordering and every code.
POST /v1/sales/batch
Purpose. Submit 1 to 100 sales in one call. Each row is validated, charged, and booked independently (partial success): one bad row never blocks its neighbors.
Request headers
Same as /v1/sales: Ocp-Apim-Subscription-Key, Authorization: Bearer {access_token}, and Content-Type: application/json.
Request body
{ "sales": [ { /* sale object */ }, { /* sale object */ } ] }
| Field | Required | Constraint |
|---|---|---|
sales | Yes | Array of sale objects (same schema as /v1/sales). Minimum 1, maximum 100. |
Success response (HTTP 200)
{
"requestId": "a1b2c3d4e5f6a7b8",
"summary": { "submitted": 2, "accepted": 1, "rejected": 1 },
"results": [
{"index": 0, "contractNumber": "H612718768", "status": "accepted", "contractId": 90432, "errors": [], "warnings": []},
{"index": 1, "contractNumber": "H612718768", "status": "rejected", "errors": [ ... ], "warnings": []}
]
}
HTTP status semantics
| Status | Meaning |
|---|---|
| 200 | The batch was processed. Inspect each row's status and errors — rows can individually be rejected even though the HTTP status is 200. This is deliberate: a 4xx would invite a blind retry that re-submits already-booked rows. |
| 422 | The whole request failed before any row was processed: empty array (50011), more than 100 rows (50010), or a malformed body (50012). |
| 401 | Missing or invalid bearer token, or (gateway) missing or invalid subscription key. |
| 429 | Gateway rate limit exceeded; nothing was processed. |
| 413 | The request body exceeds the 1 MB cap; nothing was processed. |
| 503 | A required backend dependency is down; nothing was processed. |
contractNumber claims it; any later row with the same number is
rejected with 50008, citing the earlier row's index. A row
that was itself rejected does not claim its number — a later row may still book it.GET /health
Purpose. Liveness/readiness probe; no OAuth bearer token is required. Note: no /v1 prefix.
HEAD /health is also accepted (same status, empty body) for uptime monitors. The gateway
subscription key is still required — a keyless request is rejected 401 at the gateway.
GET https://api-sandbox.archcrm.com/health
Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY
200 OK
{ "status": "ok", "environment": "sandbox" } Data Models & Enums
The sale request is a single JSON object with nested sections. Field names are camelCase.
Unknown fields are rejected (a typo'd field name is an error —
50101), not ignored. Strings are trimmed. Money fields are
decimals with at most 2 decimal places (and at most 12 digits in total); send them as JSON numbers
(for example 955.00).
Top-level structure
| Section | Required | Notes |
|---|---|---|
sale | Yes | Identity of the sale. |
coverage | Yes | Term, dates, administrator cost. |
vehicle | Conditional | Required for Auto sales; must be omitted for Home sales. |
property | Conditional | Required for Home sales; must be omitted for Auto sales. |
primaryContractHolder | Yes | The customer. |
addresses | Yes | mailing + billing always; property only for Home. |
paymentPlan | Yes | Money and schedule. |
paymentInstrument | Conditional | Required for Card/ACH; must be omitted for Invoice. |
The sale type drives everything
saleType selects the product family (Auto vs Home) and the financing style (MTM vs NonMTM),
which in turn decide which sections and fields are required or forbidden.
- MTM (Month-to-Month): a recurring plan with an
activationFee; the financed-purchase fields are not used. - NonMTM (Financed / Termed): a retail cost financed over a fixed number of installments, with a derived starting balance.
saleType | Meaning | vehicle | property | Money fields |
|---|---|---|---|---|
| 1 | Auto, MTM | Required | Forbidden | Activation set |
| 2 | Auto, NonMTM | Required | Forbidden | Financed set |
| 3 | Home, MTM | Forbidden | Required | Activation set |
| 4 | Home, NonMTM | Forbidden | Required | Financed set |
Conditional field matrix
| Rule group | Discriminator | Required | Forbidden |
|---|---|---|---|
| Item | Auto (1,2) | vehicle, vehicle.saleOdometer | property, addresses.property |
| Item | Home (3,4) | property, addresses.property | vehicle |
| Mileage | Auto NonMTM (2) | termMiles, effectiveOdometer, expiryOdometer | — |
| Money | MTM (1,3) | activationFee, activationFeeProcessMethod | retailCost, downPayment, downPaymentProcessMethod, startingBalance, numberOfInstallments, termMonths |
| Money | NonMTM (2,4) | retailCost, downPayment, downPaymentProcessMethod, startingBalance, numberOfInstallments, termMonths | activationFee, activationFeeProcessMethod |
| Property | propertyType = 2 (Multi Unit) | unitCount | — |
| Property | propertyType ≠ 2 | — | unitCount |
| Instrument | paymentMethod = 1 (Card) | card | bank |
| Instrument | paymentMethod = 2 (ACH) | bank | card |
| Instrument | paymentMethod = 3 (Invoice) | — | card, bank |
Field reference
sale
| Field | Type | Required | Constraint |
|---|---|---|---|
saleType | integer | Yes | 1–4. |
contractNumber | string | Yes | 1–25 chars. Normalized to UPPERCASE with spaces removed before the duplicate check. |
producerCode | integer | Yes | 3000–3999. |
administratorCode | integer | Yes | 1000–1999. |
insuranceCarrierCode | integer | No | 2000–2999 when present. |
saleDate | date (YYYY-MM-DD) | Yes | Not in the future. |
coverage
| Field | Type | Required | Constraint |
|---|---|---|---|
termMonths | integer | NonMTM only | > 1 and ≤ 120. Forbidden for MTM. |
effectiveDate | date | Yes | Not backdated more than 30 days. |
expirationDate | date | No | Optional; when omitted, it is derived at booking. |
administratorCost | decimal | Yes | ≥ 0, 2 dp. For NonMTM, must not exceed retailCost. |
vehicle (Auto sales)
| Field | Type | Required | Constraint |
|---|---|---|---|
vin | string | Yes | 17 characters, excludes I/O/Q; upper-cased automatically. The ISO-3779 check digit (9th character) must match (50161). |
year | integer | Yes | 1980–2099. |
make | string | Yes | 1–50 chars; cannot contain #. |
model | string | Yes | 1–100 chars; cannot contain #. |
saleOdometer | integer | All Auto | ≥ 0. |
termMiles | integer | Auto NonMTM | > 0. |
effectiveOdometer | integer | Auto NonMTM | ≥ 0. |
expiryOdometer | integer | Auto NonMTM | ≥ 0. |
property (Home sales)
| Field | Type | Required | Constraint |
|---|---|---|---|
propertyType | integer | Yes | 1 Single Family, 2 Multi Unit, 3 Condo. |
unitCount | integer | propertyType = 2 | 2–4. Forbidden otherwise. |
yearBuilt | integer | No | 1800 – current year. |
squareFootage | integer | No | 1–32000. |
primaryContractHolder
| Field | Type | Required | Constraint |
|---|---|---|---|
firstName | string | Yes | 1–30 chars. |
lastName | string | Yes | 1–30 chars. |
phone | string | Yes | 10-digit US number or E.164 (optional leading +, 10–15 digits). |
email | string | No | Valid email, ≤ 100 chars. |
preferredLanguage | integer | No | 1 English, 2 Spanish. |
addresses and the address shape
Each address slot (property, mailing, billing) has the same shape.
mailing and billing are always required. property is required for Home
sales and forbidden for Auto sales.
| Field | Type | Required | Constraint |
|---|---|---|---|
line1 | string | Yes | 1–255 chars. |
line2 | string | No | Optional; ≤ 255 chars. |
city | string | Yes | 1–50 chars. |
state | string | Yes | 2-letter code; must be a real US state or territory (USPS list: 50 states, DC, AS/GU/MP/PR/VI, AA/AE/AP). An unknown code such as ZZ is rejected. |
zipCode | string | Yes | Exactly 5 digits. |
paymentPlan
| Field | Type | Required | Constraint |
|---|---|---|---|
retailCost | decimal | NonMTM | > 0, 2 dp. |
downPayment | decimal | NonMTM | ≥ 0, 2 dp. |
downPaymentProcessMethod | integer | NonMTM | 1–3. |
startingBalance | decimal | NonMTM | > 0, 2 dp. Must equal retailCost - downPayment. |
numberOfInstallments | integer | NonMTM | ≥ 1. |
activationFee | decimal | MTM | ≥ 0, 2 dp. |
activationFeeProcessMethod | integer | MTM | 1–3. |
installmentStartDate | date | Yes | Day of month 1–28; on or after effectiveDate. |
billingCycle | integer | Yes | Must be 1 (Monthly). |
installmentAmount | decimal | Yes | > 0, 2 dp. For NonMTM, must equal startingBalance / numberOfInstallments within $0.01. |
paymentMethod | integer | Yes | 1 Card, 2 ACH, 3 Invoice. |
paymentInstrument
Provide card for Card payments, bank for ACH, neither for Invoice. Providing both,
or the wrong one, is rejected.
| Object.Field | Type | Constraint |
|---|---|---|
card.holderName | string | 1–50 chars. |
card.number | string | 15–16 digits; must pass the Luhn checksum (50315). Still subject to processor acceptance. |
card.expiration | string | MM/YYYY. The card must not be expired: an expiration month earlier than the current month (US Central) is rejected at the business stage (50313). A card is accepted through the last day of its expiration month. |
bank.accountType | integer | 1 Checking, 2 Savings. |
bank.routingNumber | string | 9 digits (ABA); must pass the ABA checksum (50316). |
bank.accountNumber | string | 6–17 digits. |
Card security codes (CVV) are not collected and are not part of the request.
Enumerations
| Enum (field) | Value | Meaning |
|---|---|---|
saleType | 1 | Auto, MTM (Month-to-Month) |
| 2 | Auto, NonMTM (Financed) | |
| 3 | Home, MTM | |
| 4 | Home, NonMTM | |
propertyType | 1 | Single Family |
| 2 | Multi Unit (requires unitCount) | |
| 3 | Condo | |
paymentMethod | 1 | Card |
| 2 | ACH (bank) | |
| 3 | Invoice (no instrument) | |
downPaymentProcessMethod / activationFeeProcessMethod | 1 | Producer / PMA — producer-managed; ArchPay does not charge at sale time. |
| 2 | Producer / FinMA — producer-managed; ArchPay does not charge at sale time. | |
| 3 | FinCo / FinMA — ArchPay charges this amount now. Card only. | |
preferredLanguage | 1 | English |
| 2 | Spanish | |
bank.accountType | 1 | Checking |
| 2 | Savings | |
billingCycle | 1 | Monthly (only accepted value) |
Response objects
Error / warning item
| code | integer — the catalog code, or null for an uncoded type error. |
|---|---|
| field | string — dotted path to the offending field (e.g. addresses.mailing.zipCode), or null. |
| message | string — human-readable description. |
requestId
(including 401, 413, and 503) — quote it when contacting support. No payment /
charge-receipt object is returned: when a due-now (FinCo) card charge is taken, it is reconciled
out of band, and a declined charge rejects the sale
(60302).Error Handling & Code Catalog
Validation order
A sale is checked in stages. Format and structure are checked first; if they fail, the later stages do not run. Within a stage, all violations are collected and returned together.
| Stage | Code range | What it checks |
|---|---|---|
| Request / batch meta | 50010–50012 | Batch size, malformed body. |
| Format & structure | 50101–50120, 50122, 50125–50161 | Field types, ranges, patterns, checksums (VIN/Luhn/ABA), required/forbidden sections per sale type. |
| Identity & duplicate | 50001–50009, 50013 | Producer/administrator/carrier exist, are active, are authorized; duplicate contract number. |
| Business math, dates & pricing | 50121, 50123–50124, 50201–50216, 50313, 51600, 60101 | Money relationships, date rules (judged on US Central time), card expiry, and the producer discount-fee pricing check. |
| Payment & booking | 50301–50316, 60301–60307 | Instrument format, processor configuration, charge outcome, booking outcome. |
Errors vs warnings
- Errors reject the sale (or batch row). Fix the input and resubmit.
- Warnings do not block booking; they ride along in the
warningsarray. Only 60101 (SALE_DATE_STALE) is currently emitted; 60102 (ADDRESS_UNVERIFIED) is reserved for a future release.
Full code catalog
HTTP status is 422 unless noted. Search by number, name, field, or message; filter by stage. Links elsewhere in this guide jump straight to a code below.
| Code | Name | HTTP | Field | Message |
|---|---|---|---|---|
| 50001 | PRODUCER_NOT_FOUND | 422 | sale.producerCode | producer not found |
| 50002 | ADMIN_NOT_FOUND | 422 | sale.administratorCode | administrator not found |
| 50003 | SALETYPE_INVALID | 422 | sale.saleType | must be 1, 2, 3 or 4 |
| 50004 | CARRIER_NOT_FOUND | 422 | sale.insuranceCarrierCode | insurance carrier not found |
| 50005 | PRODUCER_INACTIVE | 422 | sale.producerCode | producer is not active |
| 50006 | ADMIN_INACTIVE | 422 | sale.administratorCode | administrator is not active |
| 50007 | SALETYPE_PRODUCER_MISMATCH | 422 | sale.saleType | saleType does not match the producer portfolio |
| 50008 | DUPLICATE_CONTRACT_NUMBER | 422 | sale.contractNumber | contract number already exists |
| 50009 | PRODUCER_NOT_AUTHORIZED | 422 | sale.producerCode | producer is not authorized for the calling organization |
| 50010 | BATCH_TOO_LARGE | 422 | sales | batch exceeds maximum of 100 sales |
| 50011 | BATCH_EMPTY | 422 | sales | batch must contain at least 1 sale |
| 50012 | MALFORMED_REQUEST | 422 | (body) | request body is malformed |
| 50013 | CARRIER_INACTIVE | 422 | sale.insuranceCarrierCode | insurance carrier is not active |
| 50101 | UNKNOWN_FIELD | 422 | (varies) | unknown field |
| 50102 | CONTRACT_NUMBER_CONSTRAINT | 422 | sale.contractNumber | must be 1-25 characters |
| 50103 | PRODUCER_CODE_CONSTRAINT | 422 | sale.producerCode | must be between 3000 and 3999 |
| 50104 | ADMIN_CODE_CONSTRAINT | 422 | sale.administratorCode | must be between 1000 and 1999 |
| 50105 | CARRIER_CODE_CONSTRAINT | 422 | sale.insuranceCarrierCode | must be between 2000 and 2999 |
| 50106 | ADMIN_COST_CONSTRAINT | 422 | coverage.administratorCost | must be 0 or more with at most 2 decimals |
| 50107 | FIRST_NAME_CONSTRAINT | 422 | primaryContractHolder.firstName | must be 1-30 characters |
| 50108 | LAST_NAME_CONSTRAINT | 422 | primaryContractHolder.lastName | must be 1-30 characters |
| 50109 | PHONE_CONSTRAINT | 422 | primaryContractHolder.phone | must be a 10-digit US number or E.164 |
| 50110 | EMAIL_CONSTRAINT | 422 | primaryContractHolder.email | must be a valid email of at most 100 characters |
| 50111 | PREFERRED_LANGUAGE_CONSTRAINT | 422 | primaryContractHolder.preferredLanguage | must be 1 or 2 |
| 50112 | BILLING_CYCLE_CONSTRAINT | 422 | paymentPlan.billingCycle | must be 1 (Monthly) |
| 50113 | INSTALLMENT_AMOUNT_CONSTRAINT | 422 | paymentPlan.installmentAmount | must be greater than 0 with at most 2 decimals |
| 50114 | PAYMENT_METHOD_CONSTRAINT | 422 | paymentPlan.paymentMethod | must be 1, 2 or 3 |
| 50115 | MAILING_LINE1_CONSTRAINT | 422 | addresses.mailing.line1 | must not be empty |
| 50116 | MAILING_STATE_CONSTRAINT | 422 | addresses.mailing.state | must be a 2-letter state code |
| 50117 | MAILING_ZIP_CONSTRAINT | 422 | addresses.mailing.zipCode | must be 5 digits |
| 50118 | BILLING_LINE1_CONSTRAINT | 422 | addresses.billing.line1 | must not be empty |
| 50119 | BILLING_STATE_CONSTRAINT | 422 | addresses.billing.state | must be a 2-letter state code |
| 50120 | BILLING_ZIP_CONSTRAINT | 422 | addresses.billing.zipCode | must be 5 digits |
| 50121 | SALE_DATE_FUTURE | 422 | sale.saleDate | must not be in the future |
| 50122 | INSTALLMENT_DAY_INVALID | 422 | paymentPlan.installmentStartDate | day of month must be 1-28 |
| 50123 | INSTALLMENT_START_BEFORE_EFFECTIVE | 422 | paymentPlan.installmentStartDate | must be on or after the effective date |
| 50124 | EFFECTIVE_DATE_BACKDATED | 422 | coverage.effectiveDate | must not be more than 30 days in the past |
| 50125 | MAILING_ADDRESS_REQUIRED | 422 | addresses.mailing | mailing address is required |
| 50126 | BILLING_ADDRESS_REQUIRED | 422 | addresses.billing | billing address is required |
| 50127 | VIN_CONSTRAINT | 422 | vehicle.vin | must be 17 characters excluding I, O and Q |
| 50128 | VEHICLE_YEAR_CONSTRAINT | 422 | vehicle.year | must be between 1980 and 2099 |
| 50129 | MAKE_MODEL_CONSTRAINT | 422 | vehicle.make / vehicle.model | must not be empty and must not contain # |
| 50130 | VEHICLE_REQUIRED | 422 | vehicle | vehicle is required for Auto sales |
| 50131 | PROPERTY_REQUIRED | 422 | property | property is required for Home sales |
| 50132 | SALE_ODOMETER_REQUIRED | 422 | vehicle.saleOdometer | required for Auto sales |
| 50133 | TERM_MILES_REQUIRED | 422 | vehicle.termMiles | required for Auto NonMTM sales |
| 50134 | EFFECTIVE_ODOMETER_REQUIRED | 422 | vehicle.effectiveOdometer | required for Auto NonMTM sales |
| 50135 | EXPIRY_ODOMETER_REQUIRED | 422 | vehicle.expiryOdometer | required for Auto NonMTM sales |
| 50136 | PROPERTY_FORBIDDEN | 422 | property | must be omitted for Auto sales |
| 50137 | VEHICLE_FORBIDDEN | 422 | vehicle | must be omitted for Home sales |
| 50138 | PROPERTY_TYPE_CONSTRAINT | 422 | property.propertyType | must be 1, 2 or 3 |
| 50139 | UNIT_COUNT_CONSTRAINT | 422 | property.unitCount | must be between 2 and 4 |
| 50140 | YEAR_BUILT_CONSTRAINT | 422 | property.yearBuilt | must be between 1800 and the current year |
| 50141 | SQUARE_FOOTAGE_CONSTRAINT | 422 | property.squareFootage | must be between 1 and 32000 |
| 50142 | PROPERTY_ADDRESS_REQUIRED | 422 | addresses.property | required for Home sales |
| 50143 | PROPERTY_ADDRESS_FORBIDDEN | 422 | addresses.property | must be omitted for Auto sales |
| 50144 | UNIT_COUNT_REQUIRED | 422 | property.unitCount | required when propertyType is Multi Unit |
| 50145 | UNIT_COUNT_FORBIDDEN | 422 | property.unitCount | must be omitted unless propertyType is Multi Unit |
| 50146 | SALE_DATE_CONSTRAINT | 422 | sale.saleDate | must be a valid date (YYYY-MM-DD) |
| 50147 | EFFECTIVE_DATE_CONSTRAINT | 422 | coverage.effectiveDate | must be a valid date (YYYY-MM-DD) |
| 50148 | EXPIRATION_DATE_CONSTRAINT | 422 | coverage.expirationDate | must be a valid date (YYYY-MM-DD) |
| 50149 | MAILING_CITY_CONSTRAINT | 422 | addresses.mailing.city | must not be empty |
| 50150 | BILLING_CITY_CONSTRAINT | 422 | addresses.billing.city | must not be empty |
| 50151 | PROPERTY_LINE1_CONSTRAINT | 422 | addresses.property.line1 | must not be empty |
| 50152 | PROPERTY_CITY_CONSTRAINT | 422 | addresses.property.city | must not be empty |
| 50153 | PROPERTY_STATE_CONSTRAINT | 422 | addresses.property.state | must be a 2-letter state code |
| 50154 | PROPERTY_ZIP_CONSTRAINT | 422 | addresses.property.zipCode | must be 5 digits |
| 50155 | SECTION_REQUIRED | 422 | (varies) | required top-level section is missing or invalid |
| 50156 | TERM_MILES_CONSTRAINT | 422 | vehicle.termMiles | must be a positive number of miles |
| 50157 | SALE_ODOMETER_CONSTRAINT | 422 | vehicle.saleOdometer | must be a non-negative number of miles |
| 50158 | EFFECTIVE_ODOMETER_CONSTRAINT | 422 | vehicle.effectiveOdometer | must be a non-negative number of miles |
| 50159 | EXPIRY_ODOMETER_CONSTRAINT | 422 | vehicle.expiryOdometer | must be a non-negative number of miles |
| 50160 | ACTIVATION_FEE_CONSTRAINT | 422 | paymentPlan.activationFee | must be a non-negative amount |
| 50161 | VIN_CHECK_DIGIT_FAILED | 422 | vehicle.vin | fails the ISO-3779 check digit (9th character) |
| 50201 | TERM_MONTHS_CONSTRAINT | 422 | coverage.termMonths | must be greater than 1 and at most 120 |
| 50202 | RETAIL_COST_CONSTRAINT | 422 | paymentPlan.retailCost | must be greater than 0 with at most 2 decimals |
| 50203 | DOWN_PAYMENT_CONSTRAINT | 422 | paymentPlan.downPayment | must be 0 or more with at most 2 decimals |
| 50204 | STARTING_BALANCE_CONSTRAINT | 422 | paymentPlan.startingBalance | must be greater than 0 with at most 2 decimals |
| 50205 | NUMBER_OF_INSTALLMENTS_CONSTRAINT | 422 | paymentPlan.numberOfInstallments | must be 1 or more |
| 50206 | PROCESS_METHOD_CONSTRAINT | 422 | paymentPlan process method | must be 1, 2 or 3 |
| 50207 | STARTING_BALANCE_MISMATCH | 422 | paymentPlan.startingBalance | must equal retailCost minus downPayment |
| 50208 | INSTALLMENT_SPLIT_MISMATCH | 422 | paymentPlan.installmentAmount | must equal startingBalance divided by numberOfInstallments within 0.01 |
| 50210 | ADMINCOST_EXCEEDS_RETAIL | 422 | coverage.administratorCost | must not exceed retailCost |
| 50213 | NONMTM_MONEY_REQUIRED | 422 | paymentPlan | NonMTM money fields are required for NonMTM sales |
| 50214 | ACTIVATION_FEE_REQUIRED | 422 | paymentPlan.activationFee | activationFee and activationFeeProcessMethod are required for MTM sales |
| 50215 | NONMTM_MONEY_FORBIDDEN | 422 | paymentPlan | NonMTM money fields must be omitted for MTM sales |
| 50216 | ACTIVATION_FEE_FORBIDDEN | 422 | paymentPlan.activationFee | must be omitted for NonMTM sales |
| 51600 | DISCOUNT_CHART_MISSING | 422 | paymentPlan.startingBalance | no discount fee is configured for this producer, opening balance, and payment count; the sale cannot be priced. Producer configuration issue on Arch's side — contact your Arch administrator. |
| 50301 | CARD_EXPIRATION_CONSTRAINT | 422 | paymentInstrument.card.expiration | must be MM/YYYY |
| 50302 | HOLDER_NAME_CONSTRAINT | 422 | paymentInstrument.card.holderName | must be 1-50 characters |
| 50303 | ACCOUNT_TYPE_CONSTRAINT | 422 | paymentInstrument.bank.accountType | must be 1 (Checking) or 2 (Savings) |
| 50304 | BANK_ACCOUNT_INVALID | 422 | paymentInstrument.bank.accountNumber | must be 6-17 digits |
| 50305 | BANK_ROUTING_INVALID | 422 | paymentInstrument.bank.routingNumber | must be 9 digits |
| 50306 | CARD_NUMBER_INVALID | 422 | paymentInstrument.card.number | must be a valid 15-16 digit card number |
| 50307 | CARD_VAULT_FAILED | 422 | paymentInstrument.card | card could not be vaulted with the payment processor |
| 50308 | CARD_BIN_DECODE_FAILED | 422 | paymentInstrument.card.number | card brand not recognized |
| 50309 | CARD_REQUIRED | 422 | paymentInstrument.card | card is required when paymentMethod is Card |
| 50310 | BANK_REQUIRED | 422 | paymentInstrument.bank | bank is required when paymentMethod is ACH |
| 50311 | INSTRUMENT_FORBIDDEN_INVOICE | 422 | paymentInstrument | must be omitted when paymentMethod is Invoice |
| 50312 | INSTRUMENT_BOTH_PRESENT | 422 | paymentInstrument | provide card or bank, not both |
| 50313 | CARD_EXPIRED | 422 | paymentInstrument.card.expiration | card is expired. The expiration month is earlier than the current month (US Central); a card is accepted through the last day of its expiration month. |
| 50314 | PROCESS_METHOD_CARD_ONLY | 422 | paymentPlan | processMethod 3 (FinCo) is allowed only for Card payments |
| 50315 | CARD_LUHN_FAILED | 422 | paymentInstrument.card.number | card number failed the Luhn checksum |
| 50316 | BANK_ROUTING_CHECKSUM | 422 | paymentInstrument.bank.routingNumber | routing number failed the ABA checksum |
| 60101 | SALE_DATE_STALE | — | Warning | Sale date is more than 90 days old. Does not block booking. |
| 60102 | ADDRESS_UNVERIFIED | — | Warning | Reserved for a future release: address verification is not yet active, so this warning is not currently emitted. |
| 60301 | PROCESSOR_FALLBACK | 502 | Error | Payment processor unavailable; the sale was not booked. Safe to retry later. |
| 60302 | DOWNPAYMENT_ACTIVATION | 422 | Error | The card charge was declined; the sale was rejected. |
| 60303 | BOOKING_FAILED_CHARGE_REVERSED | 500 | Error | Booking failed after an approved charge; the charge was reversed automatically. Safe to resubmit. |
| 60304 | BOOKING_FAILED_REVERSAL_PENDING | 500 | Error | Booking failed after an approved charge and the reversal also failed. Do not blindly resubmit; contact Arch. |
| 60305 | BOOKING_FAILED | 500 | Error | Booking failed with no charge to reverse (nothing was charged). Safe to resubmit. |
| 60306 | PROCESSOR_NOT_CONFIGURED | 422 | Error | A due-now (FinCo) charge is owed but the producer has no payment processor configured; the sale was not booked. Permanent configuration gap — contact Arch before retrying. |
| 60307 | PROCESSOR_KEY_NOT_CONFIGURED | 422 | Error | A due-now (FinCo) charge is owed but the producer's processor has no API key assigned; the sale was not booked. Permanent configuration gap — contact Arch before retrying. |
Non-Functional Requirements
Performance & timeouts
- All calls are synchronous; the response is returned when processing completes.
- Request bodies are capped at 1 MB; a larger request is rejected with HTTP
413before any processing. - Date rules (sale date not in the future, 30-day backdate limit, 90-day stale warning) are judged on the US Central business clock, not UTC.
Pagination
Not applicable. The API has no list/collection (GET) endpoints in v1; all operations are submissions.
Idempotency & duplicate handling
- There is no client-supplied idempotency key in v1.
- Duplicate protection is server-side via the globally unique
contractNumber(across all administrators). A repeat submission is rejected with 50008. - Retry guidance: on
502or503, no sale was booked — retry with backoff. On500: 60303 and 60305 are safe to resubmit; with 60304, do not blindly retry — contact Arch. On 422 with 60306 or 60307, the producer's payment configuration is incomplete — contact Arch before retrying. For batches, a200with some rejected rows means only the rejected rows need correcting; do not resend accepted rows.
Webhooks / callbacks
None in v1. The API does not call back to partner systems; all results are returned in the synchronous response.
Security & compliance
- Transport: HTTPS only; minimum TLS 1.2; HTTP/2 enabled.
- Gateway: Azure API Management; an
Ocp-Apim-Subscription-Keyheader is required on every request (sandbox and production). - Authentication: OAuth 2.0 client credentials; HS256-signed JWT bearer tokens; 30-minute lifetime. Client secrets stored as salted PBKDF2-SHA256 hashes.
- Card data (PCI): card numbers are passed through to Arch's payment processor and tokenized (vaulted); ArchPay does not persist raw card numbers. CVV is not collected. Integrators must still handle card data in a PCI-compliant manner on their side.
- Data residency: United States.
Implementation Notes & Assumptions
Key design decisions and behaviors worth knowing when building against the API.
| Area | Note |
|---|---|
| Format / structure validation | All field and cross-field rules in Data Models & the Error Catalog are enforced. |
| Identity & duplicate checks | Producer/admin/carrier existence, active, authorization; in-batch and stored duplicate detection. |
| Business math, date & pricing rules | Money relationships, date rules, the stale-sale warning, card expiry (50313), and the producer discount-fee pricing check (51600). |
| At-sale card charge (FinCo) | FinCo (method 3) amounts are charged through the payment processor configured for the producer at sale time. A producer owing a due-now charge with no processor or processor key configured is rejected before booking (60306 / 60307). |
| Booking / persistence | The booking step writes the contract and returns contractId on the accepted response. |
Assumptions & design decisions worth knowing
contractNumberis normalized (uppercased, spaces removed) before duplicate checking, and is globally unique across all administrators.- All independent validation problems are returned together in one response (not fail-fast within a stage).
- Catalog
messagetext is canonical; runtime messages may be slightly more specific (e.g.producer 3999 is not known, or a duplicate citing the existingcontractId). - Every API response carries a
requestIdcorrelation id — including 401, 413, and 503. Gateway responses (missing/invalid subscription key 401, rate-limit 429) do not. - Auth failures return
{"status":"rejected","requestId":"...","error":"invalid_token"}with headerWWW-Authenticate: Bearer. - The token endpoint returns OAuth-style
{"error":...}for credential/availability problems but the coded envelope for a missing form field.