API reference

SecondAppraisal GAP Provider API · version 1.1.0

Referral submission and status tracking for GAP administrators, lenders and carriers, and the Garage Hub Membership roster.

  • Every request body is JSON (Content-Type: application/json). A field the schema does not name is refused with 400 unknown_field. A field a referral must never carry (an SSN, a birth date, an account number) is refused first, with 400 prohibited_field, naming the field and never its value.
  • Money is integer cents. Timestamps are ISO 8601 in UTC.
  • Every refusal the API makes is an RFC 9457 problem (application/problem+json): a stable code, the error sentence, field_errors on validation, and the request_id that is also sent as X-Request-Id. x-error-catalog lists every code.
  • During a failover, the platform can answer a write (POST or PATCH) itself, before the API sees it: 503 with Retry-After: 60 and a plain JSON body, "error": "platform_standby", with no problem members and no X-Request-Id. Nothing was written: send the same request again after Retry-After seconds, with the same Idempotency-Key where the operation takes one. Reads are not refused this way.
  • Responses are open: ignore fields you don't know.
  • A later release may add values to an enum a response carries; each such enum says what to do with a value you don't recognise.
  • Each operation states the key permission it needs (x-required-permission), its rate class (x-rate-class), its idempotency (x-idempotency), which key modes reach it (x-availability) and the most sensitive data class it carries (x-data-class, described in x-data-classes). A staged operation answers 404 operation_not_open until the switch x-availability.opens_with names opens.
  • A field marked x-requires-permission is null for a key that doesn't hold the permission it names, wherever the field appears in a response. A legacy key (sa_gap_) gets every such field its scopes reach.
  • Pipeline state is exposed through the provider-facing milestone timeline only.

This page is generated from the same contract as the OpenAPI document: OpenAPI 3.1 or OpenAPI 3.0.3, for your client generator. Each key may make 300 reads, 120 writes and 12 bulk calls a minute. Wait the number of seconds in Retry-After.

Authentication

bearerAuth

Your API key from the portal's API page: Authorization: Bearer <key>. GET /api/gap/v1/me answers any key, live or test, with what it is, what it may do and when it expires. Keys come in two formats. A legacy key starts sa_gap_ (sa_gap_test_ for a test key). A v2 key, which the portal issues once v2 keys open, is sa_live_ or sa_test_, then 30 letters and digits and a 6-character checksum; a v2 key whose checksum doesn't match gets 401 missing_api_key before any lookup. A v2 key holds only the permissions chosen when it was created, from a preset (Intake only, Intake + status, Reporting, Membership roster) or one by one: each operation names the permission it needs in x-required-permission, and a v2 key without it gets 403 permission_denied. If v2 keys can't be checked just now, the call gets 503 credentials_unavailable; retry after the seconds in Retry-After. A legacy key's scopes decide what it reaches: no scopes reaches everything; referrals the referral and analytics operations; plans the membership plan operations; read the plan list operations and analytics. Outside them it gets 403 key_not_scoped. Every key reaches the events feed, which lists each event type only to a key holding that type's permission, and an event's referral, beyond its id, only to a key holding referrals:read. Test keys (sa_gap_test_...) work only with the membership plan operations, GET /api/gap/v1/me and the events feed's plan events until the referral sandbox opens: until then a referral or analytics call with one gets 403 test_key_referrals_unavailable. Once it opens, a test key reaches sandbox referrals only, and a live key never sees them. A v2 test key (sa_test_...) is a test key in the same way. A test key expires at most 90 days after it is created, a v2 live key at most 365 days after, and a legacy live key only when we schedule its retirement, with notice; an expired key gets 401 api_key_expired. Each key may make 300 reads, 120 writes and 12 bulk calls a minute (x-rate-class); over that is 429 with Retry-After. A signed credential sends no bearer key: it signs each request instead (signedRequest). An institution can ask us to accept signed requests only: from then a call with an API key gets 401 signed_requests_required. A key, bearer or signed, can hold an IP allowlist: a call from an address outside it, as our edge saw it, gets 403 ip_not_allowed. A request that carries a Signature-Input header is read as a signed one, whatever else it carries, so a bearer call must carry none: with one, even a valid key's call gets 401 signature_profile_invalid. Such a call counts as failed authentication from your address, so past the limit it can be 429 auth_failures_throttled. Don't let a proxy, gateway or agent add an HTTP message signature of its own to your bearer calls.

signedRequest

A signed credential: the institution's own Ed25519 or ECDSA P-256 public key, registered in the portal as a JWK or a BEGIN PUBLIC KEY PEM (for P-256, a named-curve (prime256v1) SubjectPublicKeyInfo with an uncompressed point), signing every request with RFC 9421 HTTP Message Signatures and RFC 9530 Content-Digest (the gap-v2 profile). It holds the permissions chosen when it was registered, as a v2 key does, and expires as one does. A new credential is activated by one signed POST /api/gap/v1/credentials/{id}/verify; until then any other call gets 401 credential_not_activated. Each request carries Signature-Input and Signature (one signature, labelled sa), X-SA-Environment (TEST for a test credential, PRODUCTION for a live one) and no Authorization header: a request carrying both credentials is ambiguous, and is 401 signature_profile_invalid however valid either is. The signature covers @method and @target-uri, then content-digest and idempotency-key on POST and PATCH, idempotency-key on GET and HEAD whenever it is sent, then x-sa-environment. Its parameters are created (unix seconds), nonce (16 to 128 of A-Z a-z 0-9 _ -, new for every request), keyid (the credential's id), alg (ed25519 or ecdsa-p256-sha256, the credential's own) and tag="gap-v2", in that order. @target-uri is https://, the host you called in lower case (secondappraisal.com, gap.secondappraisal.com or lenders.secondappraisal.com), then the path and query of the request URL as a WHATWG URL parser serializes it (new URL() in JavaScript), which is how we read every request: never the request line as your client wrote it. Percent-encode each query value with RFC 3986's unreserved characters only (a ' is %27), percent-encode non-ASCII as its UTF-8 bytes, and send no empty ? and no . or .. path segment; a percent-escape you send is kept as sent, its hex digits' case included. On a signed request Idempotency-Key is 1 to 255 visible ASCII characters (! to ~), no spaces. Content-Digest is sha-256=:<base64>: over the exact body bytes, a string's as UTF-8. An Ed25519 signature is its 64 bytes; a P-256 signature is the 64-byte r||s, never DER. Both are sent as sa=:<base64>:. created must be within 300 seconds before our clock and 30 after it. A nonce is used once per credential: it is spent when the signature has verified, the body has matched Content-Digest, the request has come from an address the credential's IP allowlist holds (when it holds one) and the credential was within its rate limit, whatever the request is then answered. A 403 ip_not_allowed, a 503 credentials_unavailable because the allowlist couldn't be decided, a 429 rate_limited, or a 503 platform_standby because the nonce itself couldn't be written, leaves it unspent. A retry is signed again, with a new nonce and the same Idempotency-Key. Refused before any credential is looked up: 401 signature_profile_invalid, signature_expired or target_uri_not_allowed. Refused the same way whatever failed: 401 signature_invalid, before any byte of the body is read, or once the signature verifies, for a body that doesn't match Content-Digest. Either counts as failed authentication from your address, a revoked credential's included, so past the limit it can be 429 auth_failures_throttled, and neither spends the nonce. Then, in order: 413 payload_too_large or 400 invalid_json for a body too large or too slow to arrive, which spends nothing either; 403 ip_not_allowed for an address outside the credential's IP allowlist, or 503 credentials_unavailable when the allowlist can't be decided, which spend nothing either; 429 rate_limited; 409 signature_replay for a nonce already used; 401 api_key_expired, credential_not_activated or environment_mismatch.

At launchSigned credentials, v2 keys and their permissions open with the partner security platform. Until then the portal creates legacy live keys only. A legacy key keeps working after launch: a legacy live key until we schedule its retirement, with notice, and a legacy test key until the expiry GET /api/gap/v1/me reports: 90 days after it was created, or, for a key made before keys had an expiry, 90 days after we stamped it; we may schedule its retirement sooner, with notice.

At launchIP allowlists and signed requests only are enforced from launch. A call from an address outside its key's allowlist gets 403 ip_not_allowed once the origin lock and the partner security platform enforce, and a call with an API key from an institution that accepts signed requests only gets 401 signed_requests_required once the partner security platform enforces. Until then, a call either one would refuse is answered and only logged.

Referrals

Refer a borrower, follow the referral, edit or cancel it.

Create a referral

POST /api/gap/v1/referrals

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

Submits one borrower referral. Requires an executed MSA; provider-paid and split-pay referrals also need a billing method. Send an Idempotency-Key header to make retries safe: a replay returns the original referral with idempotent_replay: true and 200. Keys are per institution and per key mode: the same key sent with a live key and a test key makes two referrals, and a live key can't send a key that starts with test: (409 idempotency_key_mode_conflict). A key that doesn't hold referrals:read gets an acknowledgement instead of the referral: its id, object and status, never its data. Such a key's replay must repeat its own request exactly: a key that came with a different request, or with one sent before requests were fingerprinted, is 422 idempotency_key_reused. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
referrals:write
Rate class
write
Idempotency
Idempotency-Key header, at most 255 characters; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringMakes a retry safe: a request with a key your institution already used returns the original result with idempotent_replay: true. At most 255 characters, trimmed; a longer key is refused, never cut. On a signed request (signedRequest) it is required and signed: 1 to 255 visible ASCII characters (! to ~), no spaces, or the call gets 401 signature_profile_invalid.

Request body

ReferralCreateInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field). A field a referral must never carry is refused before anything else is checked (prohibited_field).

Responses

Members a refusal may add

  • existing_referral_id (string) on duplicate_referral, only to a key holding referrals:read. On duplicate_referral: your referral for the same loss. Never sent to a key that doesn't hold referrals:read.
  • state (string) on state_not_served. On state_not_served: the state judged, a two-letter code.
  • basis (string) on state_not_served. On state_not_served: garaged when the state judged is garaged_state, loss when it is loss_state.
Error codes (39)

List referrals

GET /api/gap/v1/referrals

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

Your referrals, newest first, a page at a time. Once a borrower withdraws permission to report a referral's progress to you, the referral shows status: reporting_withdrawn and reporting_withdrawn_at, the fields you supplied, and no progress. status matches the status shown: reporting_withdrawn lists those referrals, and any other status leaves them out. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
referrals:read
Rate class
read
Idempotency
None needed
Data class
borrower_contact

Parameters

NameInTypeDescription
statusqueryarray of stringComma-separated statuses, e.g. invited,activated. Case-insensitive. ?status= is no filter; a value naming no status (,, or only spaces) is refused. A status matches the status a referral is shown with: reporting_withdrawn lists the referrals whose borrower withdrew permission to report their progress, and any other status leaves them out.One of: submitted, invited, handoff_pending, outreach_queued, contact_attempted, activated, in_progress, settled, closed, declined, unreachable, expired, cancelled, reporting_withdrawn
external_refdeprecatedquerystringExact match on the external_ref you sent, trimmed. ?external_ref= is no filter; only spaces is refused. Deprecated: the reference travels in the URL, where the systems it passes through can record it. It still filters; retrieve a referral by the id its create returned instead.
limitqueryintegerPage size, 1 to 100, written in digits. A blank value means the default.
starting_afterquerystringCursor: the previous page's next_cursor (its last referral's id, or with updated_after an opaque u1. cursor). ?starting_after= is the first page; only spaces is refused.
updated_afterquerystring (date-time)Only the referrals changed after this time (ISO 8601 with its offset, such as 2026-10-01T00:00:00Z, from 0001-01-01T00:00:00Z through 9999-12-31T23:59:59.999Z), oldest change first; a referral that changes again before you reach it moves to a later page. A referral whose borrower withdraws permission to report its progress is listed once more, at reporting_withdrawn_at, and never again. A change is listed about a minute after it happens. Every page, the last and an empty one too, returns an opaque u1. next_cursor: to poll, keep the latest one and send it as starting_after with the same updated_after, never a time from your own clock, which misses the changes made in the minute before each poll. A cursor works only for the institution and mode it was issued to; if one is refused (400 invalid_cursor), start again from your updated_after without it. ?updated_after= is no filter.

Responses

  • 200: ReferralList. A newest-first page.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (24)

Retrieve a referral

GET /api/gap/v1/referrals/{id}

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

The full referral, with the provider-facing milestone timeline and the financial panel. Once a borrower withdraws permission to report a referral's progress to you, the referral shows status: reporting_withdrawn and reporting_withdrawn_at, the fields you supplied, and no progress. Its timeline and financial are then null, as are consultation_number and days_in_negotiation. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
referrals:read
Rate class
read
Idempotency
None needed
Data class
borrower_contact

Parameters

NameInTypeDescription
idrequiredpathstringThe referral's id.

Responses

  • 200: ReferralDetail. The referral.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (24)

Update or cancel a referral

PATCH /api/gap/v1/referrals/{id}

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

loan_payoff_cents and deductible_cents are editable any time. Every other field, and the program terms, until the borrower activates (409 after). { "action": "cancel" }, sent on its own, cancels before activation (409 after). A referral whose borrower withdrew permission to report its progress is 409 reporting_withdrawn, whatever the request asks, and nothing is written. A key that doesn't hold referrals:read gets an acknowledgement instead of the referral: its id, object and status, never its data. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
referrals:write
Rate class
write
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
idrequiredpathstringThe referral's id.
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

ReferralPatchInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field). A field a referral must never carry is refused before anything else is checked (prohibited_field).

Responses

  • 200: ReferralDetail or ReferralAcknowledgement. The updated referral. For a key that doesn't hold referrals:read: an acknowledgement, never the referral.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.

Members a refusal may add

  • state (string) on state_not_served. On state_not_served: the state judged, a two-letter code.
  • basis (string) on state_not_served. On state_not_served: garaged when the state judged is garaged_state, loss when it is loss_state.
Error codes (37)

Create referrals in bulk

POST /api/gap/v1/referrals/bulk

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

Up to 500 referrals in one call. Each row is validated and created on its own: valid rows are created even when siblings fail, and results reports every row. A row may carry its own idempotency_key and, for a warm handoff, its disclosure. An institution without an executed MSA gets 403 msa_required for the whole call; a provider-paid or split-pay row without a billing method fails with the row code billing_required. A key that doesn't hold referrals:read gets each row's referral as an acknowledgement (its id, object and status), and its rows replay only their own requests, as a single create's do. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
referrals:write
Rate class
bulk
Idempotency
A per-row key, at most 255 characters; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it: each row's idempotency_key is what makes a retry safe.

Request body

ReferralBulkInput (application/json, required). Content-Type: application/json, at most 1,048,576 bytes. A field the schema does not name is refused (unknown_field). A field a referral must never carry is refused before anything else is checked (prohibited_field).

Responses

Error codes (31)

Simulate a sandbox referral's next step

POST /api/gap/v1/referrals/{id}/simulate

At launchIt opens with the referral sandbox. Until then every call answers 404 operation_not_open. Test keys only.

Test keys only. Moves one of your sandbox referrals one step along its lifecycle, and sends the webhook event a live referral sends at that step (referral.invited, referral.activated, referral.declined or settlement.updated) to your test endpoints, marked livemode: false. Nothing reaches a borrower and nothing is billed. A key that doesn't hold referrals:read gets an acknowledgement instead of the referral: its id, object and status, never its data. A live key gets 403 simulate_requires_test_key, an id that is not one of your sandbox referrals 404, and a step off the lifecycle 409 invalid_transition. A referral whose borrower withdrew permission to report its progress is 409 reporting_withdrawn, whatever the request asks, and nothing is written. Staged: it opens with the referral sandbox, and until then every call answers 404 operation_not_open.

Authentication
An API key or a signed credential
Permission
referrals:write
Rate class
write
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
idrequiredpathstringThe referral's id.
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

ReferralSimulateInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field).

Responses

  • 200: ReferralDetail or ReferralAcknowledgement. The sandbox referral after the step. For a key that doesn't hold referrals:read: an acknowledgement, never the referral.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (33)

Attest a warm handoff

POST /api/gap/v1/referrals/{id}/attest

At launchIt opens with the referral sandbox. Until then every call answers 404 operation_not_open. Live and test keys.

For a warm-handoff referral created without its disclosure: you certify that your staff delivered the disclosure script and that the borrower agreed to be contacted, the same attestation as create's disclosure, and the referral is queued for our outreach. Accepted only while the referral waits for its attestation: after that, or for an invitation referral, it is a 409 invalid_transition. A script version that is not the current script is a 409 script_version_stale. A referral whose borrower withdrew permission to report its progress is 409 reporting_withdrawn, whatever the request asks, and nothing is written. A test key attests its sandbox referrals, which reach no one: no outreach is opened for them. A key that doesn't hold referrals:read gets an acknowledgement instead of the referral: its id, object and status, never its data. Staged: it opens with the referral sandbox, and until then every call answers 404 operation_not_open.

Authentication
An API key or a signed credential
Permission
referrals:write
Rate class
write
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
idrequiredpathstringThe referral's id.
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

DisclosureInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field). A field a referral must never carry is refused before anything else is checked (prohibited_field).

Responses

  • 200: ReferralDetail or ReferralAcknowledgement. The referral, queued for our outreach. For a key that doesn't hold referrals:read: an acknowledgement, never the referral.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (34)

Look up review status by VIN, claim number or reference

POST /api/gap/v1/review-status/lookup

At launchIt opens with review records. Until then every call answers 404 operation_not_open. Live and test keys.

Finds your referrals by one identifier, sent in the body so it never travels in a URL: vin, claim_number or external_ref, as you sent it on the referral. Each match comes with the latest version of its review record, cited as { id, version, hash } with its status and when it was written, and with its close reason: what the review webhooks carry. Read the version itself with GET /api/gap/v1/referrals/{id}/review-record. Newest first, at most 100 (has_more says more matched); no match is an empty list. record is null while a referral's record has no version to show: before its first, and from the borrower's request that we stop reporting the referral's progress until the version that records it. It writes nothing, but during a failover the platform can still answer it as it answers a write (503 platform_standby). Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them. Staged: it opens with review records, once their sweep is on and has finished its first pass; until then every call answers 404 operation_not_open.

Authentication
An API key or a signed credential
Permission
referrals:read
Rate class
read
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_personal

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

ReviewStatusLookupInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field).

Responses

  • 200: ReviewStatusList. Your matching referrals, newest first, each with its review status.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (27)

Retrieve a referral's review record

GET /api/gap/v1/referrals/{id}/review-record

At launchIt opens with review records. Until then every call answers 404 operation_not_open. Live and test keys.

One version of the referral's review record: the latest, or the one ?version= names. A version is written each time the record changes and never edited, and its hash is the SHA-256 of bundle's canonical JSON (RFC 8785), so the version you read later still matches the hash a webhook or the lookup cited. bundle holds the record: its status, the verdict once it reached the borrower and whether we can help (never a dollar figure), the review window, outreach, a refusal, a review done elsewhere, the appraisal right, the costs by component and payer, the requirement, the outcome and the copy versions. A redacted version keeps its id, version, hash and status, with bundle: null. Once the borrower asks us to stop reporting the referral's progress, the latest version says only that, and the versions before it stay readable by number; until the version that records it is written, there is no latest to show. 404 not_found for a referral that isn't yours in this key's mode, a version it doesn't have, or a record with no version to show yet. costs.engagement and costs.institution_charges are null unless the key also holds reporting:read. The hash covers the whole record, costs included, so a copy with them null still cites the version but doesn't hash to it. Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them. Staged: it opens with review records, once their sweep is on and has finished its first pass; until then every call answers 404 operation_not_open.

Authentication
An API key or a signed credential
Permission
referrals:read
Rate class
read
Idempotency
None needed
Data class
borrower_personal

Parameters

NameInTypeDescription
idrequiredpathstringThe referral's id.
versionqueryintegerThe version to read, 1 for the record's first, written in digits. Leave it out, or blank, for the latest the record can show.

Responses

  • 200: ReviewRecord. The version.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (25)

Analytics

Your funnel, outcomes and spend.

Savings and spend analytics

GET /api/gap/v1/analytics

Available. Live keys only.

At launchTest keys reach sandbox referrals here once the sandbox opens.

Your referral funnel and activation rate, settled outcomes (uplift, exposure reduction), spend (fees, ROI multiple, net savings), and breakdowns by market and carrier: the numbers the portal's Analytics page shows. Filter on the referral's creation date with from (inclusive) and to (exclusive). Test keys (sa_gap_test_... or sa_test_...) get 403 test_key_referrals_unavailable until the referral sandbox opens; from then on a test key reaches sandbox referrals only, and a live key never sees them.

Authentication
An API key or a signed credential
Permission
reporting:read
Rate class
read
Idempotency
None needed
Data class
institution_confidential

Parameters

NameInTypeDescription
fromquerystring (date)Referrals created on or after this date (UTC), YYYY-MM-DD. ?from= is no bound. Spaces around it are ignored.
toquerystring (date)Referrals created before this date (UTC), YYYY-MM-DD: the bound is exclusive. Must be after from. ?to= is no bound. Spaces around it are ignored.

Responses

  • 200: AnalyticsReport. The report.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (23)

Garage Hub Membership

The Garage Hub Membership: the vehicles you enroll, the consultations drawn against them and your monthly statements. The membership is not insurance: nothing is paid to a member and no result is promised.

Enroll one vehicle

POST /api/gap/v1/plans/enrollments

Available. Live and test keys.

Idempotent per institution and key mode with the Idempotency-Key header: a replay returns the original with idempotent_replay: true and 200; a key already used in the other mode or for another VIN is a 422 idempotency_key_reused. Partner-billed institutions get an ACTIVE enrollment at once (you are the payer of record); direct-collect institutions get a PENDING one with an activation_url for the member.

Authentication
An API key or a signed credential
Permission
plans:write
Rate class
write
Idempotency
Idempotency-Key header, at most 255 characters; a signed request sends and signs an Idempotency-Key
Data class
credential

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringMakes a retry safe: a request with a key your institution already used returns the original result with idempotent_replay: true. At most 255 characters, trimmed; a longer key is refused, never cut. On a signed request (signedRequest) it is required and signed: 1 to 255 visible ASCII characters (! to ~), no spaces, or the call gets 401 signature_profile_invalid.

Request body

PlanEnrollmentInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field).

Responses

Error codes (42)

List your roster

GET /api/gap/v1/plans/enrollments

Available. Live and test keys.

Your enrollments in this key's mode, newest first. A key scoped to read may list, and an institution whose membership gate is closed can still read its roster.

Authentication
An API key or a signed credential
Permission
plans:read
Rate class
read
Idempotency
None needed
Data class
credential

Parameters

NameInTypeDescription
vindeprecatedquerystringOnly this VIN: 17 characters; case, spaces, dashes and dots are ignored. The check digit is not checked. ?vin= is no filter. Deprecated: the VIN travels in the URL, where the systems it passes through can record it. It still filters; retrieve an enrollment by the id its create returned instead.
statusquerystringOnly this status, case-insensitive. ?status= is no filter.One of: PENDING, ACTIVE, PAST_DUE, LAPSED, CANCELLED
cursorquerystringThe previous page's next_cursor. ?cursor= is the first page; only spaces is refused.
limitqueryintegerPage size, 1 to 200, written in digits. A blank value means the default.

Responses

  • 200: PlanEnrollmentList. A newest-first page.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (24)

Retrieve one enrollment

GET /api/gap/v1/plans/enrollments/{id}

Available. Live and test keys.

One of your enrollments in this key's mode.

Authentication
An API key or a signed credential
Permission
plans:read
Rate class
read
Idempotency
None needed
Data class
credential

Parameters

NameInTypeDescription
idrequiredpathstringThe enrollment's id.

Responses

  • 200: PlanEnrollment. The enrollment.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (25)

Cancel, swap the VIN, or edit contact details

PATCH /api/gap/v1/plans/enrollments/{id}

Available. Live and test keys.

{ "cancel": true }, on its own, cancels: at once for a partner-billed enrollment, at period end for a direct-collect one. { "new_vin": … } enrolls the new VIN and closes this enrollment (201): the new vehicle's eligibility window starts again; garaged_state, external_ref and each member field carry over unless sent, and a paying direct-collect member's subscription moves to the new enrollment. Any other body edits external_ref and member only; a body with nothing to apply is a 400. In external_ref, send your own enrollment or file ID. Never a loan, account or policy number (a credit union member number is an account number).

Authentication
An API key or a signed credential
Permission
plans:write
Rate class
write
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
credential

Parameters

NameInTypeDescription
idrequiredpathstringThe enrollment's id.
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

PlanEnrollmentPatchInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field).

Responses

  • 200: PlanEnrollment. The cancelled or edited enrollment.
  • 201: PlanEnrollmentSwapResult. Swapped: the new enrollment, naming the one it replaced.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (44)

Enroll up to 500 vehicles

POST /api/gap/v1/plans/enrollments/bulk

Available. Live and test keys.

Each row is validated and enrolled on its own; results reports every row. A row's idempotency_key replays it like the single endpoint's header. An Idempotency-Key header (at most 251 characters) keys every row without its own as <header>:<index>, so re-sending the same batch replays it.

Authentication
An API key or a signed credential
Permission
plans:write
Rate class
bulk
Idempotency
Idempotency-Key header (at most 251 characters), or a per-row key (at most 255); a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringA batch key: every row without its own idempotency_key is keyed <header>:<index>, so re-sending the same batch replays it. At most 251 characters, trimmed. On a signed request (signedRequest) it is required and signed: 1 to 255 visible ASCII characters (! to ~), no spaces, or the call gets 401 signature_profile_invalid.

Request body

PlanEnrollmentBulkInput (application/json, required). Content-Type: application/json, at most 1,048,576 bytes. A field the schema does not name is refused (unknown_field).

Responses

Error codes (35)

Reconcile your full roster

POST /api/gap/v1/plans/roster-sync

Available. Live and test keys.

Every VIN in the list ends up live; every live VIN of yours not in the list is cancelled (you stopped billing that member), at period end for a paying direct-collect member. Only an explicit { "enrollments": [] } empties the roster; a bare array is refused. Any malformed row refuses the whole sync (400 with rejected), so a parse error can never cancel a live member. Built for a nightly job from a loan-servicing system. In external_ref, send your own enrollment or file ID. Never a loan, account or policy number (a credit union member number is an account number).

Authentication
An API key or a signed credential
Permission
plans:write
Rate class
bulk
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_contact

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

PlanRosterSyncInput (application/json, required). Content-Type: application/json, at most 1,048,576 bytes. A field the schema does not name is refused (unknown_field).

Responses

  • 200: PlanRosterSyncResult. What the sync did.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.

Members a refusal may add

  • rejected (array of PlanRosterRejection) on validation_failed. On validation_failed for rows: every row that failed, by index, with its field errors. Nothing was changed.
Error codes (34)

Tell us a member vehicle was declared a total loss

POST /api/gap/v1/plans/loss-notices

Available. Live and test keys.

The payoff-request trigger: you usually know before the member does. Records the notice on the enrollment so we can reach the member with a running start. 404 not_a_member_vehicle when the VIN is not a live member vehicle of yours; a date_of_loss more than a day ahead is a 400.

Authentication
An API key or a signed credential
Permission
plans:write
Rate class
write
Idempotency
None needed with a bearer key; a signed request sends and signs an Idempotency-Key
Data class
borrower_personal

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringOn a signed request (signedRequest) it is required and signed, and must be 1 to 255 visible ASCII characters (! to ~), with no spaces, or the call gets 401 signature_profile_invalid. A call with an API key may leave it out or send any value. This operation keeps no record of it, so it makes nothing idempotent here.

Request body

PlanLossNoticeInput (application/json, required). Content-Type: application/json, at most 65,536 bytes. A field the schema does not name is refused (unknown_field).

Responses

Error codes (35)

Member consultations drawn against your roster

GET /api/gap/v1/plans/redemptions

Available. Live and test keys.

Ids, VIN, status, dates and the outcome once settled: never the consultation's internals. Newest first, a page at a time. A redemption names the referral its member consultation made (referral_id); once the borrower withdraws permission to report that referral's progress to you, the redemption shows status: REPORTING_WITHDRAWN and reporting_withdrawn_at, with no outcome and no settled_at, as the referral itself reads (status: reporting_withdrawn).

Authentication
An API key or a signed credential
Permission
plans:read
Rate class
read
Idempotency
None needed
Data class
borrower_personal

Parameters

NameInTypeDescription
cursorquerystringThe previous page's next_cursor. ?cursor= is the first page; only spaces is refused.
limitqueryintegerPage size, 1 to 200, written in digits. A blank value means the default.

Responses

  • 200: PlanRedemptionList. A newest-first page.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (24)

Your monthly membership statements

GET /api/gap/v1/plans/statements

Available. Live and test keys.

Your last 36 monthly statements, newest first. A test key gets an empty list: statements bill only live enrollments.

Authentication
An API key or a signed credential
Permission
plans:read
Rate class
read
Idempotency
None needed
Data class
institution_confidential

Responses

  • 200: PlanStatementList. Your statements.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (24)

Events

What happened to your referrals and enrollments, for polling instead of, or beside, webhooks.

List your events

GET /api/gap/v1/events

At launchIt opens with the partner security platform. Until then every call answers 404 operation_not_open. Live and test keys.

Your institution's events from the last 30 days, oldest first: every event we record for you, whether or not a webhook endpoint takes it, as a thin event (ids, your external_ref, statuses, the milestone reached, outcome codes and timestamps; never a borrower's contact details, a payoff, a VIN or an amount). Each event type needs its own permission: the referral and review events referrals:read, charge.created reporting:read, and the membership plan events plans:read. A key lists only the types it holds the permission for, and a key that holds none of them gets an empty list; a key without referrals:read gets each event's referral as its id only. The membership plan events are listed only while the membership program is open to your institution, and a test key lists the referral and charge events only once the referral sandbox opens, as those operations answer. The review events (review.completed, review.updated and referral.closed) are listed only while review records are open, as the review-status lookup and the review record answer. Once a borrower withdraws permission to report a referral's progress, its events from before the withdrawal stay listed, and from the withdrawal on, only the event that announces it is listed (referral.closed, or review.updated citing the REPORTING_WITHDRAWN version); its charge.created and the membership plan's enrollment and statement events stay, while plan.benefit.redeemed and plan.benefit.completed follow the referral the redemption made. A live key lists live events, and a test key sandbox ones. An event is listed about a minute after it happens. To poll, keep the next_cursor every page returns and send it as starting_after, never an event's id: an event can reach the feed after a newer one's webhook has gone out, so a position taken from a webhook would skip it. Webhooks and the feed carry the same event ids, so drop duplicates by id across both. Which types are listed is decided when you read: the events of a surface closed then are not listed later to a poller that has read past them. To see them, or after a 400 invalid_cursor (a cursor works only for the institution and mode it was issued to), start again without starting_after: the feed lists the last 30 days, and you drop the ids you have. Staged: it opens with the partner security platform, and until then every call answers 404 operation_not_open.

Authentication
Any API key or signed credential, live or test
Permission
None
Rate class
read
Idempotency
None needed
Data class
institution_confidential

Parameters

NameInTypeDescription
limitqueryintegerPage size, 1 to 100, written in digits. A blank value means the default.
starting_afterquerystringThe previous page's next_cursor: an opaque position, the last event this feed listed to you; the page starts after it. Never an event's id, not even one a webhook delivered: an event can reach the feed after a newer one's webhook has gone out, so a position taken from a webhook would skip it. A cursor works only for the institution and mode it was issued to; anything else is 400 invalid_cursor. ?starting_after= is the first page; only spaces is refused.

Responses

  • 200: EventFeedPage. An oldest-first page of thin events.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (22)

The OpenAPI document

This document.

This OpenAPI document

GET /api/gap/v1/openapi

Available. Reached without a key.

The OpenAPI 3.1 document for this API, generated from the contract the API runs on. No key needed. ?oas=3.0 serves a derived OpenAPI 3.0.3 copy.

Authentication
No key needed
Permission
None
Rate class
none
Idempotency
None needed
Data class
public

Parameters

NameInTypeDescription
oasquerystring3.0 serves the derived OpenAPI 3.0.3 copy, for tooling that predates 3.1.One of: 3.1, 3.0

Responses

  • 200: OpenApiDocument. The document.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (3)

This OpenAPI document (.json alias)

GET /api/gap/v1/openapi.json

Available. Reached without a key.

The same document as GET /api/gap/v1/openapi, under a path ending in .json for tools that expect one.

Authentication
No key needed
Permission
None
Rate class
none
Idempotency
None needed
Data class
public

Parameters

NameInTypeDescription
oasquerystring3.0 serves the derived OpenAPI 3.0.3 copy, for tooling that predates 3.1.One of: 3.1, 3.0

Responses

  • 200: OpenApiDocument. The document.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (3)

Keys and credentials

The key making the call: what it is, what it may do and when it expires; and a signed credential's activation.

The key making this call

GET /api/gap/v1/me

Available. Live and test keys.

Which API key made the call and what it may do: its id and prefix, live or test, its format and how it authenticates, the permissions it holds (a legacy key's scopes as the permissions they reach), when it expires, your institution's id and status, and how many address ranges its IP allowlist holds. Any key reaches it, live or test, whatever it may do, so it is the call to make first when a key is refused elsewhere. A terminated institution's key gets 403 provider_terminated.

Authentication
Any API key or signed credential, live or test
Permission
None
Rate class
read
Idempotency
None needed
Data class
institution_confidential

Responses

  • 200: ApiKeyIntrospection. The key.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (20)

Activate a signed credential

POST /api/gap/v1/credentials/{id}/verify

At launchIt opens when signed credentials open. Until then every call answers 404 operation_not_open. Live and test signed credentials.

A signed credential registered in the portal starts unactivated: until this call succeeds, every other call it signs gets 401 credential_not_activated. Sign this call with the credential itself, {id} being its id (the keyid it signs with), with an empty body: the signature proves you hold the private key of the public key you registered. It answers the credential with activated_at, and a later call answers the same time. A public key is active for one credential at most: if another credential has already activated it, this call gets 409 public_key_in_use, and you register a new key pair instead. Any other caller, a bearer key or another credential, gets 404. Staged: it opens when signed credentials open, and until then every call answers 404 operation_not_open.

Authentication
A signed credential, signing this call about itself
Permission
None
Rate class
write
Idempotency
Idempotency-Key required and signed; this call keeps no record of it
Data class
institution_confidential

Parameters

NameInTypeDescription
idrequiredpathstringThe signed credential's id: the keyid its signatures name.
Idempotency-KeyrequiredheaderstringOn a signed request (signedRequest), the only kind this call takes, it is required and signed: 1 to 255 visible ASCII characters (! to ~), no spaces, or the call gets 401 signature_profile_invalid. This call keeps no record of it: it is idempotent itself, and a retry is signed again, with a new nonce.

Responses

  • 200: CredentialActivation. The credential, activated.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (26)

Check a signature (test credentials)

POST /api/gap/v1/signature-check

Planned: not served yet. It is documented ahead of its route. Test signed credentials only.

Planned. For a test credential while you build your signer: send a signed request here and it answers whether the signature verified, with the signature base built from the request as received, to compare byte for byte with yours, and the code the same request would be refused with elsewhere. Test credentials only. Not served yet.

Authentication
A signed credential, signing this call about itself
Permission
None
Rate class
read
Idempotency
Idempotency-Key required and signed; this call keeps no record of it
Data class
institution_confidential

Parameters

NameInTypeDescription
Idempotency-KeyrequiredheaderstringOn a signed request (signedRequest), the only kind this call takes, it is required and signed: 1 to 255 visible ASCII characters (! to ~), no spaces, or the call gets 401 signature_profile_invalid. This call keeps no record of it: it is idempotent itself, and a retry is signed again, with a new nonce.

Responses

  • 200: SignatureCheckResult. What the signature looked like to the API.
  • The API's refusals: an RFC 9457 problem (Problem) with one of the codes below.
  • The platform's own answers come before the API's, in plain JSON with an error member and no code: platform_standby (503) to a write during a failover, and edge_auth_required (403) once the origin lock enforces, at launch; and our edge's service_unavailable (503) during an outage.
Error codes (21)

Webhook events

Every delivery is a signed POST. The data shapes listed here are the full payloads an endpoint created before launch receives, in the WebhookEvent envelope. Verifying webhooks shows how to check each signature.

At launchA new endpoint receives thin payloads instead: each event as ThinEvent, the same thin event the events feed lists, with ids, statuses and timestamps. Fetch the record for the rest.

  • referral.invited · The borrower was sent the activation invitation. Data: WebhookReferralInvitedData
  • referral.activated · The borrower activated: the consultation exists. Data: WebhookReferralActivatedData
  • referral.declined · The borrower declined. Data: WebhookReferralData
  • consultation.status_changed · The referral's provider-facing milestone moved. Data: objectThe referral and the milestone it reached. Provisional: the key set is not frozen yet, so read it defensively.
  • appraisal.completed · Our appraisal report is complete. Data: objectThe referral and the milestone it reached. Provisional: the key set is not frozen yet, so read it defensively.
  • settlement.updated · The final settlement was recorded. Data: WebhookSettlementData
  • charge.created · A charge for the referral was created. Data: objectThe referral and the charge (id, amount_cents, status). A charge on a referral whose borrower withdrew permission to report its progress still comes, with the referral as every read shows it: status: reporting_withdrawn and no progress. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.enrollment.activated · A membership enrollment became active. Data: objectThe enrollment's id, VIN, external_ref, status and dates; never the member's contact details. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.enrollment.past_due · A direct-collect membership's renewal is failing. Data: objectThe enrollment's id, VIN, external_ref, status and dates; never the member's contact details. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.enrollment.cancelled · A membership enrollment was cancelled or lapsed. Data: objectThe enrollment's id, VIN, external_ref, status and dates; never the member's contact details. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.benefit.redeemed · A member's consultation was drawn against their membership. Data: objectThe enrollment, the redemption id and the consultation number. Not sent once the borrower withdrew permission to report the progress of the referral the redemption names. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.benefit.completed · A member consultation finished. Data: objectThe enrollment, the redemption id and the outcome. Not sent once the borrower withdrew permission to report the progress of the referral the redemption names. Provisional: the key set is not frozen yet, so read it defensively.
  • plan.statement.issued · A monthly membership statement was issued. Data: objectThe statement, as the statements operation returns it. Provisional: the key set is not frozen yet, so read it defensively.
  • review.completed · The referral's review completed: its review record has a new version. Data: WebhookReviewRecordData
  • review.updated · A review record already complete or closed has a new version. Data: WebhookReviewRecordData
  • referral.closed · The referral closed: its review record's new version gives the close reason. Data: WebhookReviewRecordData

Schemas

AnalyticsReport

Your referral funnel, settled outcomes, spend and breakdowns by market and carrier: the numbers the portal's Analytics page shows.

Fields (47)
FieldTypeDescription
objectrequired"gap.analytics"
filtersrequiredobject
filters.fromrequiredstring (date-time) or nullThe from you sent, as an instant.
filters.torequiredstring (date-time) or nullThe to you sent, as an instant.
funnelrequiredobject
funnel.submittedrequiredinteger
funnel.awaiting_activationrequiredinteger
funnel.activerequiredinteger
funnel.settledrequiredinteger
funnel.declinedrequiredinteger
funnel.unreachablerequiredinteger
funnel.expiredrequiredinteger
funnel.cancelledrequiredinteger
funnel.reporting_withdrawnrequiredintegerReferrals whose borrower withdrew permission to report their progress to you: counted here and in submitted, never under a later status, in activation_rate or among the outcomes.
funnel.activation_raterequirednumber or nullActivated ÷ (activated + closed without activation), 0 to 1; null before any referral closes.
outcomesrequiredobject
outcomes.settled_casesrequiredinteger
outcomes.total_uplift_centsrequiredinteger
outcomes.avg_uplift_centsrequiredinteger or null
outcomes.total_exposure_before_centsrequiredinteger or nullNull for a key without referrals.contact:read.
outcomes.total_exposure_after_centsrequiredinteger or nullNull for a key without referrals.contact:read.
outcomes.total_exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.
outcomes.avg_exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.
spendrequiredobject
spend.fees_paid_centsrequiredinteger
spend.fees_pending_centsrequiredinteger
spend.roi_multiplerequirednumber or nullExposure reduction ÷ fees paid; null without both.Null for a key without referrals.contact:read.
spend.net_savings_centsrequiredinteger or nullNull for a key without referrals.contact:read.
by_marketrequiredarray of object
by_market[].keyrequiredstringThe state code or carrier name; unknown when the referral has none.
by_market[].referralsrequiredinteger
by_market[].settledrequiredinteger
by_market[].uplift_centsrequiredinteger
by_market[].exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.
by_carrierrequiredarray of object
by_carrier[].keyrequiredstringThe state code or carrier name; unknown when the referral has none.
by_carrier[].referralsrequiredinteger
by_carrier[].settledrequiredinteger
by_carrier[].uplift_centsrequiredinteger
by_carrier[].exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.
time_seriesrequiredarray of object
time_series[].monthrequiredstringThe UTC month, YYYY-MM.
time_series[].submittedrequiredinteger
time_series[].activatedrequiredinteger
time_series[].settledrequiredinteger
time_series[].uplift_centsrequiredinteger
time_series[].exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.

ApiKeyIntrospection

The API key that made the call: what it is, what it may do, and when it expires. Never the key itself.

Fields (12)
FieldTypeDescription
idrequiredstringThe key's id, the one the portal's API page lists it under.
objectrequired"gap.api_key"
prefixrequiredstringThe key's first characters, as the portal shows them: enough to tell your keys apart, never enough to use one. A signed credential's starts pk:, then the start of its public key's thumbprint.
moderequiredstringlive, or test for a sandbox key. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: live, test
formatrequiredstringlegacy for an sa_gap_ key, v2 for an sa_live_ or sa_test_ key or a signed credential. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: legacy, v2
auth_methodrequiredstringsecret: the key is sent as a bearer token. public_key: a signed credential, whose requests are signed with its registered public key's private half. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: secret, public_key
permissionsrequiredarray of stringWhat the key may do. A v2 key holds the permissions chosen when it was created. A legacy key's scopes are given as the permissions they reach: no scopes reach every one. referrals.contact:read and members:activation-link are field permissions: they open no operation, and a key without one reads null in each field whose x-requires-permission names it, in whatever its operations answer. A legacy read key lists referrals.contact:read for the analytics' figures built on the loan payoff and the roster's member emails, and a legacy plans key for the roster's member emails. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: referrals:write, referrals:read, referrals.contact:read, reporting:read, plans:read, plans:write, members:activation-link
expires_atrequiredstring (date-time) or nullWhen the key stops working; null for a legacy live key with no retirement scheduled.
institutionrequiredobject
institution.idrequiredstringYour institution's id.
institution.statusrequiredstringactive; requested before approval; suspended, which can read but not write; terminated. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: requested, active, suspended, terminated
allowed_cidr_countrequiredintegerHow many address ranges the key's IP allowlist holds; 0 when it has none.

BulkRowAcknowledgementError

Fields (5)
FieldTypeDescription
fieldrequiredstringThe row's field, program, row for the row as a whole, or body for a row that is not an object.
messagerequiredstring
codestringA field-error code (unknown_field, invalid_type, required, prohibited_field, …), or a refusal code such as email_required_sms_disabled, script_version_stale, billing_required, idempotency_key_mode_conflict, idempotency_key_reused, state_not_served or duplicate_referral.
statestringOn state_not_served: the state judged.
basisstringOn state_not_served: garaged when the state judged is garaged_state, loss when it is loss_state. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: garaged, loss

BulkRowError

Fields (6)
FieldTypeDescription
fieldrequiredstringThe row's field, program, row for the row as a whole, or body for a row that is not an object.
messagerequiredstring
codestringA field-error code (unknown_field, invalid_type, required, prohibited_field, …), or a refusal code such as email_required_sms_disabled, script_version_stale, billing_required, idempotency_key_mode_conflict, idempotency_key_reused, state_not_served or duplicate_referral.
existing_referral_idstringOn duplicate_referral: your referral for the same loss. Never sent to a key that doesn't hold referrals:read.
statestringOn state_not_served: the state judged.
basisstringOn state_not_served: garaged when the state judged is garaged_state, loss when it is loss_state. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: garaged, loss

CredentialActivation

A signed credential, activated: it can sign any call its permissions reach.

Fields (5)
FieldTypeDescription
idrequiredstringThe signed credential's id.
objectrequired"gap.signed_credential"
moderequiredstringlive, or test for a sandbox credential. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: live, test
algrequiredstringThe algorithm its key was registered with, which every signature it makes must name: ed25519 or ecdsa-p256-sha256. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: ed25519, ecdsa-p256-sha256
activated_atrequiredstring (date-time)When the credential's first verification call succeeded. A later call answers the same time.

DisclosureInput

Warm-handoff attestation. Accepted only with consent_mode: "warm_handoff".

Fields (5)
FieldTypeDescription
confirmedrequiredtrueMust be true.
channelrequiredstringHow the script was delivered.One of: phone, in_person, email, video, other
attestor_namerequiredstringThe name of the person on your staff who delivered the script.
delivered_onstring (date) or nullThe date the script was delivered; omit for today. At most a day ahead (a timezone ahead of UTC) and no more than 90 days old. Spaces around it are ignored.
script_versionstring or nullThe disclosure script version your staff delivered, as the script names it. The referral is stamped with it; omit it to attest the current script. A version that is not the current script is refused with 409 script_version_stale (a row-level error on bulk).

EventFeedPage

Fields (4)
FieldTypeDescription
objectrequired"list"
datarequiredThinEventarray of ThinEvent
has_morerequiredboolean
next_cursorrequiredstringEvery page has one, the last and an empty one too: where your next poll resumes. Keep the latest and send it as starting_after; has_more says whether to fetch the next page now.

FieldError

One field that failed validation.

Fields (4)
FieldTypeDescription
fieldrequiredstringThe field, in the API's own names: a dotted path inside the body (program.subsidy_value, member.email), or the query or path parameter's name. A rule across fields names the rule (borrower_contact). An error about the body as a whole is body: a body that is not an object, or an edit refused without naming one field.
coderequiredstringWhat is wrong with the field. Branch on this, not on message. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: unknown_field, unknown_parameter, duplicate_parameter, invalid_type, required, too_long, too_large, too_small, invalid_format, invalid_value, prohibited_field
messagerequiredstringA sentence a person can act on.
pointerstringRFC 6901 JSON Pointer to the value in the request body (/program/mode); "", the whole body, when field is body. Absent for query and path parameters, and for a rule across fields such as borrower_contact, which names no member of the body.

OpenApiDocument

This document.

Fields (4)
FieldTypeDescription
openapirequiredstring
inforequiredobject
info.titlerequiredstring
info.versionrequiredstring

PlanEnrollment

A membership enrollment (snake_case). Ignore fields you don't know.

Fields (25)
FieldTypeDescription
idrequiredstring
vinrequiredstring
statusrequiredstringPENDING (direct-collect, awaiting the member's activation), ACTIVE, PAST_DUE, LAPSED or CANCELLED. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: PENDING, ACTIVE, PAST_DUE, LAPSED, CANCELLED
billing_moderequiredstringPARTNER_BILLED: you pay the wholesale price per vehicle-month and bill the member yourself. DIRECT_COLLECT: we charge the member's card your price and settle the difference with you monthly. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: PARTNER_BILLED, DIRECT_COLLECT
external_refrequiredstring or null
vehiclerequiredobject
vehicle.yearrequiredinteger or null
vehicle.makerequiredstring or null
vehicle.modelrequiredstring or null
vehicle.garaged_staterequiredstring or null
memberrequiredobject
member.first_namerequiredstring or null
member.last_namerequiredstring or null
member.emailrequiredstring or nullNull for a key without referrals.contact:read.
member_price_centsrequiredinteger0 for partner-billed rows.
wholesale_centsrequiredinteger or null
started_atrequiredstring (date-time) or null
eligible_fromrequiredstring (date-time) or nullThe instant from which a total loss on this vehicle qualifies for the member consultation (thirty days after the membership starts).
current_period_endrequiredstring (date-time) or null
cancel_at_period_endrequiredboolean
cancelled_atrequiredstring (date-time) or null
activation_urlrequiredstring or nullDirect-collect only, while PENDING: the co-branded page where the member activates. Anyone holding it can activate, so send it only to the member.Null for a key without members:activation-link.
activation_expires_atrequiredstring (date-time) or null
testrequiredbooleanCreated through a test key.
created_atrequiredstring (date-time)

PlanEnrollmentBulkInput

Fields (1)
FieldTypeDescription
enrollmentsrequiredPlanEnrollmentBulkRowarray of PlanEnrollmentBulkRow1 to 500 vehicles. Each row is validated and enrolled on its own.

PlanEnrollmentBulkResult

Fields (16)
FieldTypeDescription
receivedrequiredinteger
createdrequiredintegerRows enrolled now (replays are not counted).
resultsrequiredarray of object
results[].indexrequiredinteger0-based index into enrollments.
results[].vinrequiredstringThe row's VIN as sent (normalized when valid).
results[].okrequiredboolean
results[].idstring
results[].statusstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: PENDING, ACTIVE, PAST_DUE, LAPSED, CANCELLED
results[].idempotent_replayboolean
results[].codestringOn a failed row: validation_failed, or the refusal code the single endpoint would answer.
results[].messagestring
results[].field_errorsarray of object
results[].field_errors[].fieldrequiredstring
results[].field_errors[].messagerequiredstring
results[].field_errors[].codestring
results[].field_errors[].pointerstring

PlanEnrollmentBulkRow

One vehicle in a bulk enrollment.

Fields (7)
FieldTypeDescription
vinrequiredstringThe 17-character VIN, case-insensitive. Checked before anything is enrolled: 17 characters, no I, O or Q, and a valid check digit (422 vin_invalid, or a field error).
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
garaged_statestring or nullThe two-letter state where the vehicle is garaged, case-insensitive. Required to enroll: a row without it is refused with 422 state_required, and a state where SecondAppraisal can't act as the appraiser with 422 state_blocked.
memberPlanMemberInputPlanMemberInput or null
vehiclePlanVehicleInputPlanVehicleInput or null
consent_modestring or nullCase-insensitive.One of: invitation, warm_handoff
idempotency_keystring or nullReplays this row like the single endpoint's Idempotency-Key header. A row without one is keyed <header>:<index> when the call sends an Idempotency-Key header.

PlanEnrollmentCancelInput

Cancel the enrollment. Nothing else may be sent with it.

Fields (1)
FieldTypeDescription
cancelrequiredtrueCancels the enrollment: at once for a partner-billed row, at period end for a direct-collect row with a subscription.

PlanEnrollmentCreateResult

Every field of PlanEnrollment, and:

Fields (1)
FieldTypeDescription
idempotent_replayrequiredbooleanTrue when this answers a replayed Idempotency-Key with the original enrollment.

PlanEnrollmentEditInput

Edit the reference or the member's contact details. A body with nothing to apply is a 400; member: null, cancel: false or null and new_vin: null apply nothing.

Fields (4)
FieldTypeDescription
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
memberPlanMemberInputPlanMemberInput or nullNull is the same as leaving member out.
cancelfalse or nullfalse or null is the same as leaving cancel out. To cancel, send { "cancel": true } on its own.
new_vinnullNull is the same as leaving new_vin out. To swap the vehicle, send the new VIN.

PlanEnrollmentInput

One vehicle to enroll. The VIN is the key, and the garaged state is required; everything else is optional. A vehicle that has been the subject of a total-loss consultation with SecondAppraisal cannot be enrolled (422 vin_already_consulted); a VIN with a live membership anywhere cannot be enrolled twice (409 vin_already_live).

Fields (6)
FieldTypeDescription
vinrequiredstringThe 17-character VIN, case-insensitive. Checked before anything is enrolled: 17 characters, no I, O or Q, and a valid check digit (422 vin_invalid, or a field error).
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
garaged_statestring or nullThe two-letter state where the vehicle is garaged, case-insensitive. Required to enroll: a row without it is refused with 422 state_required, and a state where SecondAppraisal can't act as the appraiser with 422 state_blocked.
memberPlanMemberInputPlanMemberInput or null
vehiclePlanVehicleInputPlanVehicleInput or null
consent_modestring or nullCase-insensitive.One of: invitation, warm_handoff

PlanEnrollmentList

Fields (2)
FieldTypeDescription
datarequiredPlanEnrollmentarray of PlanEnrollment
next_cursorrequiredstring or nullPass as cursor for the next page; null on the last page.

PlanEnrollmentPatchInput

One of: { "cancel": true } alone; a VIN swap (new_vin, with optional carry-over fields); or edits to external_ref and member.

One of PlanEnrollmentCancelInput or PlanEnrollmentSwapInput or PlanEnrollmentEditInput.

PlanEnrollmentSwapInput

Move the member to another vehicle: the new VIN is enrolled and this enrollment closes. garaged_state, external_ref and each member field carry over unless sent; a paying direct-collect member's subscription moves to the new row.

Fields (7)
FieldTypeDescription
new_vinrequiredstringThe 17-character VIN, case-insensitive. Checked before anything is enrolled: 17 characters, no I, O or Q, and a valid check digit (422 vin_invalid, or a field error). The member moves to this vehicle, whose eligibility window starts again.
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
garaged_statestring or nullThe two-letter state where the vehicle is garaged, case-insensitive. Required to enroll: a row without it is refused with 422 state_required, and a state where SecondAppraisal can't act as the appraiser with 422 state_blocked.
memberPlanMemberInputPlanMemberInput or null
vehiclePlanVehicleInputPlanVehicleInput or null
consent_modestring or nullCase-insensitive.One of: invitation, warm_handoff
cancelfalse or nullfalse or null is the same as leaving cancel out. To cancel, send { "cancel": true } on its own.

PlanEnrollmentSwapResult

Every field of PlanEnrollment, and:

Fields (1)
FieldTypeDescription
swapped_fromrequiredstringThe enrollment this one replaced.

PlanLossNoticeInput

A member vehicle was declared a total loss.

Fields (4)
FieldTypeDescription
vinrequiredstringThe member vehicle's VIN, case-insensitive.
date_of_lossstring (date) or nullYYYY-MM-DD; at most a day ahead (a timezone ahead of UTC). Null, or leaving it out, records no date. Spaces around it are ignored.
claim_numberstring or null
carrierstring or nullThe member's insurer.

PlanLossNoticeResult

Fields (4)
FieldTypeDescription
enrollment_idrequiredstring
statusrequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: PENDING, ACTIVE, PAST_DUE, LAPSED, CANCELLED
eligible_fromrequiredstring (date-time) or null
recordedrequiredtrue

PlanMemberInput

The member's contact details. Every field is optional.

Fields (4)
FieldTypeDescription
first_namestring or null
last_namestring or null
emailstring or nullAn email address of at most 254 characters, with no spaces, a single @, and a domain with a dot that has text on each side (example.com). Spaces around it are ignored. Stored in lowercase.
phonestring or null

PlanRedemption

A member consultation drawn against your roster. Never the consultation's internals, and none of its progress once the borrower withdrew permission to report it.

Fields (14)
FieldTypeDescription
idrequiredstring
enrollment_idrequiredstring
external_refrequiredstring or null
referral_idrequiredstring or null
vinrequiredstring
statusrequiredstringWhere the redemption stands: PENDING, APPROVED, DENIED or COMPLETED. REPORTING_WITHDRAWN once the borrower withdrew permission to report the progress of the referral it names (referral_id), whatever happens after: the referral itself then reads status: reporting_withdrawn. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: PENDING, APPROVED, DENIED, COMPLETED, REPORTING_WITHDRAWN
date_of_lossrequiredstring (date-time) or null
value_centsrequiredinteger
initial_offer_centsrequiredinteger or null
final_settlement_centsrequiredinteger or null
uplift_centsrequiredinteger or null
settled_atrequiredstring (date-time) or null
created_atrequiredstring (date-time)
reporting_withdrawn_atrequiredstring (date-time) or nullWhen the borrower withdrew permission to report the progress of the referral this redemption names; null while they haven't, or it names none. From then on the redemption shows status: REPORTING_WITHDRAWN, and its outcome (initial_offer_cents, final_settlement_cents, uplift_cents) and settled_at are null.

PlanRedemptionList

Fields (2)
FieldTypeDescription
datarequiredPlanRedemptionarray of PlanRedemption
next_cursorrequiredstring or null

PlanRosterRejection

Fields (7)
FieldTypeDescription
indexrequiredinteger
vinrequiredstring
field_errorsrequiredarray of object
field_errors[].fieldrequiredstring
field_errors[].messagerequiredstring
field_errors[].codestring
field_errors[].pointerstring

PlanRosterSyncInput

Fields (1)
FieldTypeDescription
enrollmentsrequiredPlanEnrollmentInputarray of PlanEnrollmentInputYour full roster, at most 500 vehicles. An empty array cancels every live enrollment.

PlanRosterSyncResult

Fields (8)
FieldTypeDescription
receivedrequiredinteger
enrolledrequiredarray of stringVINs enrolled now.
already_liverequiredarray of stringVINs that were already live.
refusedrequiredarray of object
refused[].vinrequiredstring
refused[].coderequiredstringThe refusal code the single endpoint would answer.
refused[].messagerequiredstring
cancelledrequiredarray of stringVINs cancelled because they were not in the roster.

PlanStatement

One calendar month's membership statement, in integer cents. direction says who owes whom: partner_owes (wholesale plus any member-fee subsidy, billed as one charge), we_owe_partner (the direct-collect spread above wholesale, remitted by transfer) or settled.

Fields (18)
FieldTypeDescription
idrequiredstring
period_startrequiredstring (date)
period_endrequiredstring (date)
statusrequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: ISSUED, SETTLED, VOID
vehicle_monthsrequirednumber
active_vehicles_endrequiredinteger
redemption_countrequiredinteger
wholesale_centsrequiredinteger
collected_centsrequiredinteger
rev_share_centsrequiredinteger
subsidy_centsrequiredinteger
net_centsrequiredintegerPositive: you owe us; negative: we owe you.
directionrequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: partner_owes, we_owe_partner, settled
charge_idrequiredstring or null
transfer_idrequiredstring or null
linesrequiredarray of object or nullThe statement's lines. Their fields are not frozen yet; ignore any you don't know.
issued_atrequiredstring (date-time)
settled_atrequiredstring (date-time) or null

PlanStatementList

Fields (1)
FieldTypeDescription
datarequiredPlanStatementarray of PlanStatement

PlanVehicleInput

The vehicle's description. Every field is optional.

Fields (4)
FieldTypeDescription
yearinteger or null
makestring or null
modelstring or null
trimstring or null

Problem

RFC 9457 problem details, sent as application/problem+json, with the API's legacy error string and a stable code beside the standard members. Some operations add members of their own, named in their responses.

Fields (9)
FieldTypeDescription
errorrequiredstringThe reason, in the wording the API has always used. Kept for existing clients; branch on code.
coderequiredstringStable reason code. It never changes with the wording; x-error-catalog lists every code. New values may be added: treat a code you don't recognise by the response's HTTP status.One of: invalid_json, unsupported_media_type, payload_too_large, too_many_rows, prohibited_field, validation_failed, operation_not_open, idempotency_key_too_long, internal_error, missing_api_key, invalid_api_key, api_key_expired, key_not_scoped, test_key_referrals_unavailable, provider_terminated, provider_suspended, provider_not_active, rate_limited, auth_unavailable, permission_denied, credentials_unavailable, signed_requests_required, ip_not_allowed, auth_failures_throttled, signature_profile_invalid, signature_expired, target_uri_not_allowed, signature_invalid, environment_mismatch, credential_not_activated, signature_replay, public_key_in_use, edge_auth_required, platform_standby, membership_program_closed, membership_not_enabled, membership_rider_unsigned, membership_billing_mode_required, membership_billing_method_required, msa_required, billing_required, email_required_sms_disabled, script_version_stale, duplicate_reference, invalid_cursor, not_found, referral_locked, cancel_not_allowed, economics_locked, nothing_to_update, idempotency_key_mode_conflict, state_not_served, duplicate_referral, simulate_requires_test_key, invalid_transition, reporting_withdrawn, vin_already_live, vin_already_consulted, state_required, state_blocked, vin_invalid, invalid_customer_price, idempotency_key_reused, plan_not_enabled, not_live, not_a_member_vehicle, request_refused
typerequiredstringRFC 9457 problem type: a URI naming the code. It identifies the problem; there is no need to fetch it.
titlerequiredstringRFC 9457: a short summary of the code.
statusrequiredintegerRFC 9457: the HTTP status, repeated.
detailrequiredstringRFC 9457: what went wrong with this request.
instancerequiredstringRFC 9457: the path that was called, without its query string.
request_idrequiredstringThe request id, also sent as the X-Request-Id header. Quote it when you contact us.
field_errorsFieldErrorarray of FieldErrorOn validation_failed and prohibited_field: each field that failed, and why.

Referral

A referral as the API returns it: snake_case, grouped sub-objects. Ignore fields you don't know.

Fields (45)
FieldTypeDescription
idrequiredstringThe referral's id.
objectrequired"gap.referral"
statusrequiredstringWhere the referral stands. reporting_withdrawn once the borrower withdrew permission to report its progress to you, whatever happens to it after: from then on it shows the fields you supplied and no progress. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: submitted, invited, handoff_pending, outreach_queued, contact_attempted, activated, in_progress, settled, closed, declined, unreachable, expired, cancelled, reporting_withdrawn
status_labelrequiredstringThe status as the portal shows it.
status_tonerequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: green, yellow, red, neutral
status_noterequiredstring or nullA third-person note on a terminal status, such as why it closed.
external_refrequiredstring or nullYour own reference, as you sent it.
borrowerrequiredobject
borrower.first_namerequiredstring
borrower.last_namerequiredstring
borrower.emailrequiredstring or nullNull for a key without referrals.contact:read.
borrower.phonerequiredstring or null10 digits.Null for a key without referrals.contact:read.
vehiclerequiredobject
vehicle.vinrequiredstring or null
vehicle.yearrequiredinteger or null
vehicle.makerequiredstring or null
vehicle.modelrequiredstring or null
claimrequiredobject
claim.carrierrequiredstring or null
claim.claim_numberrequiredstring or null
claim.loss_staterequiredstring or null
claim.date_of_lossrequiredstring (date-time) or nullMidnight UTC on the date of loss.
claim.initial_offer_centsrequiredinteger or null
liabilityrequiredobject
liability.loan_payoff_centsrequiredinteger or nullNull for a key without referrals.contact:read.
liability.deductible_centsrequiredinteger or null
programrequiredobject
program.moderequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: provider_paid, split_pay, customer_paid, membership
program.subsidy_typerequiredstring or nullNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: percent, fixed_cents
program.subsidy_valuerequiredinteger or null
program.price_centsrequiredintegerYour price for this referral's consultation, in cents, fixed when the referral was submitted. mode and the subsidy terms decide how much of it you pay.
program.lockedrequiredboolean or nullTrue once the borrower activated under these terms; they can no longer change. Null once reporting is withdrawn (status: reporting_withdrawn).
consentrequiredobject
consent.moderequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: invitation, warm_handoff
consent.disclosure_confirmed_atrequiredstring (date-time) or null
consent.disclosure_channelrequiredstring or null
consent.disclosure_attestor_namerequiredstring or nullNull for a key without referrals.contact:read.
submitted_viarequiredstringWhere the referral came from: api, dashboard or csv.
created_atrequiredstring (date-time)
invited_atrequiredstring (date-time) or null
invitation_expires_atrequiredstring (date-time) or null
activated_atrequiredstring (date-time) or null
settled_atrequiredstring (date-time) or null
closed_atrequiredstring (date-time) or null
reporting_withdrawn_atrequiredstring (date-time) or nullWhen the borrower withdrew permission to report this referral's progress to you; null while they haven't. From then on the referral shows status: reporting_withdrawn and no progress: its invited, expiry, activated, settled and closed times and program.locked are null.

ReferralAcknowledgement

What a write answers a key that doesn't hold referrals:read: the referral's id and status, never its data. A key that holds referrals:read reads the referral itself.

Fields (3)
FieldTypeDescription
idrequiredstringThe referral's id.
objectrequired"gap.referral"
statusrequiredstringNew values may be added: show one you don't recognise as not yet known, and don't fail.One of: submitted, invited, handoff_pending, outreach_queued, contact_attempted, activated, in_progress, settled, closed, declined, unreachable, expired, cancelled, reporting_withdrawn

ReferralBulkAcknowledgement

Fields (9)
FieldTypeDescription
objectrequired"bulk_result"
createdrequiredintegerRows created now (replays are not counted).
failedrequiredinteger
resultsrequiredarray of object
results[].indexrequiredinteger0-based index into referrals.
results[].okrequiredboolean
results[].referralReferralAcknowledgement
results[].idempotent_replayboolean
results[].errorsBulkRowAcknowledgementErrorarray of BulkRowAcknowledgementError

ReferralBulkInput

Fields (1)
FieldTypeDescription
referralsrequiredReferralBulkRowarray of ReferralBulkRow1 to 500 referrals. Each row is validated and created on its own.

ReferralBulkResult

Fields (9)
FieldTypeDescription
objectrequired"bulk_result"
createdrequiredintegerRows created now (replays are not counted).
failedrequiredinteger
resultsrequiredarray of object
results[].indexrequiredinteger0-based index into referrals.
results[].okrequiredboolean
results[].referralReferral
results[].idempotent_replayboolean
results[].errorsBulkRowErrorarray of BulkRowError

ReferralBulkRow

One referral in a bulk create.

Fields (28)
FieldTypeDescription
borrower_first_namerequiredstring
borrower_last_namerequiredstring
borrower_emailstring or nullAn email address of at most 254 characters, with no spaces, a single @, and a domain with a dot that has text before it and at least two characters after it (example.com). Spaces around it are ignored. Stored in lowercase. At least one of borrower_email and borrower_phone is required. An invitation referral (the default consent_mode) needs an email while text invitations are off for its loss state: one without it is refused with 422 email_required_sms_disabled. A warm-handoff referral may be phone-only.
borrower_phonestring or nullA 10-digit US number; punctuation and a leading 1 are tolerated, and it is stored as 10 digits.
consent_modestring or nullinvitation (the default): we send the borrower a co-branded activation link. warm_handoff: your staff delivered the disclosure script and the borrower agreed to be contacted; send the attestation as disclosure. Case-insensitive.One of: invitation, warm_handoff
vinstring or null11 to 17 letters and digits, never I, O or Q. Case-insensitive; stored in uppercase.
vehicle_yearinteger or nullA model year from 1950 to two years after the current year.
vehicle_makestring or null
vehicle_modelstring or null
primary_carrierstring or nullThe borrower's insurer.
claim_numberstring or nullThe borrower's claim number with that insurer.
loss_statestring or nullThe two-letter state of the loss, case-insensitive (tx is TX).
garaged_statestring or nullThe two-letter USPS code of the state where the vehicle is garaged (a state or DC), case-insensitive. We judge this state when it is sent, else loss_state: a referral for a state we don't serve may be refused with 422 state_not_served.
date_of_lossstring (date) or nullThe date of the loss; at most a day ahead (a timezone ahead of UTC). Spaces around it are ignored.
initial_offer_centsinteger or nullThe insurer's initial ACV offer, in cents.
loan_payoff_centsinteger or nullThe outstanding loan payoff, in cents. It drives your exposure figures and stays editable after activation.
deductible_centsinteger or nullThe deductible, in cents. Stays editable after activation.
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
programReferralProgramInputReferralProgramInput or nullPer-referral program terms. Omit, or send null, to use your program defaults.
disclosureDisclosureInputDisclosureInput or nullWarm-handoff attestation, only with consent_mode: "warm_handoff". With it the referral is created ready for our outreach; without it the referral waits for your attestation.
claim_againststring or nullWhose insurer is handling the claim: own (the borrower's own insurer), other_driver (another driver's insurer), or unknown. Case-insensitive. Leave it out when you don't know.One of: own, other_driver, unknown
cause_of_lossstring or nullThe cause of the loss, if you know it. weather is hail, wind or a storm; a flood is flood. Case-insensitive.One of: collision, theft, fire, flood, weather, vandalism, animal, other
triggerstring or nullWhat led you to refer: payoff_request (an insurer asked you for the loan payoff), loss_notice (a notice of loss), gap_claim (the borrower filed a GAP claim), borrower_request (the borrower asked), or other. Case-insensitive.One of: payoff_request, loss_notice, gap_claim, borrower_request, other
payoff_requested_atstring or nullWhen the insurer asked you for the loan payoff: a date (2026-06-16, read as midnight UTC) or an ISO 8601 timestamp with its UTC offset (2026-06-16T15:04:00-06:00). At most a day ahead.
payoff_request_channelstring or nullHow that payoff request reached you. web_portal is your own online portal; electronic_service is a third-party payoff or letter-of-guarantee service the insurer used. Case-insensitive.One of: phone, fax, email, mail, web_portal, electronic_service, other
client_namestring or nullFor a GAP administrator: the lender or dealer whose borrower this is. At most 120 characters.
requirement_basisRequirementBasisInputRequirementBasisInput or nullSend it only when the borrower's contract lets you require the review. It is recorded and checked against your program's bases; messages to the borrower still ask rather than require.
idempotency_keystring or nullMakes this row safe to retry: a row with a key already used by your institution, in this key's mode, returns the original referral with idempotent_replay: true. A string of at most 255 characters; a longer key is refused, never cut. A live key can't send one that starts with test: (the row fails with idempotency_key_mode_conflict).

ReferralCancelInput

Cancel the referral. Nothing else may be sent with it.

Fields (1)
FieldTypeDescription
actionrequired"cancel"Cancels the referral. Allowed only before the borrower activates (409 after).

ReferralCreateAcknowledgement

Every field of ReferralAcknowledgement, and:

Fields (1)
FieldTypeDescription
idempotent_replayrequiredbooleanTrue when this answers a replay of this same request under its Idempotency-Key.

ReferralCreateInput

One borrower referral. Rules across fields: at least one of borrower_email and borrower_phone; a split-pay program needs both subsidy terms; disclosure only with a warm handoff.

Fields (27)
FieldTypeDescription
borrower_first_namerequiredstring
borrower_last_namerequiredstring
borrower_emailstring or nullAn email address of at most 254 characters, with no spaces, a single @, and a domain with a dot that has text before it and at least two characters after it (example.com). Spaces around it are ignored. Stored in lowercase. At least one of borrower_email and borrower_phone is required. An invitation referral (the default consent_mode) needs an email while text invitations are off for its loss state: one without it is refused with 422 email_required_sms_disabled. A warm-handoff referral may be phone-only.
borrower_phonestring or nullA 10-digit US number; punctuation and a leading 1 are tolerated, and it is stored as 10 digits.
consent_modestring or nullinvitation (the default): we send the borrower a co-branded activation link. warm_handoff: your staff delivered the disclosure script and the borrower agreed to be contacted; send the attestation as disclosure. Case-insensitive.One of: invitation, warm_handoff
vinstring or null11 to 17 letters and digits, never I, O or Q. Case-insensitive; stored in uppercase.
vehicle_yearinteger or nullA model year from 1950 to two years after the current year.
vehicle_makestring or null
vehicle_modelstring or null
primary_carrierstring or nullThe borrower's insurer.
claim_numberstring or nullThe borrower's claim number with that insurer.
loss_statestring or nullThe two-letter state of the loss, case-insensitive (tx is TX).
garaged_statestring or nullThe two-letter USPS code of the state where the vehicle is garaged (a state or DC), case-insensitive. We judge this state when it is sent, else loss_state: a referral for a state we don't serve may be refused with 422 state_not_served.
date_of_lossstring (date) or nullThe date of the loss; at most a day ahead (a timezone ahead of UTC). Spaces around it are ignored.
initial_offer_centsinteger or nullThe insurer's initial ACV offer, in cents.
loan_payoff_centsinteger or nullThe outstanding loan payoff, in cents. It drives your exposure figures and stays editable after activation.
deductible_centsinteger or nullThe deductible, in cents. Stays editable after activation.
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
programReferralProgramInputReferralProgramInput or nullPer-referral program terms. Omit, or send null, to use your program defaults.
disclosureDisclosureInputDisclosureInput or nullWarm-handoff attestation, only with consent_mode: "warm_handoff". With it the referral is created ready for our outreach; without it the referral waits for your attestation.
claim_againststring or nullWhose insurer is handling the claim: own (the borrower's own insurer), other_driver (another driver's insurer), or unknown. Case-insensitive. Leave it out when you don't know.One of: own, other_driver, unknown
cause_of_lossstring or nullThe cause of the loss, if you know it. weather is hail, wind or a storm; a flood is flood. Case-insensitive.One of: collision, theft, fire, flood, weather, vandalism, animal, other
triggerstring or nullWhat led you to refer: payoff_request (an insurer asked you for the loan payoff), loss_notice (a notice of loss), gap_claim (the borrower filed a GAP claim), borrower_request (the borrower asked), or other. Case-insensitive.One of: payoff_request, loss_notice, gap_claim, borrower_request, other
payoff_requested_atstring or nullWhen the insurer asked you for the loan payoff: a date (2026-06-16, read as midnight UTC) or an ISO 8601 timestamp with its UTC offset (2026-06-16T15:04:00-06:00). At most a day ahead.
payoff_request_channelstring or nullHow that payoff request reached you. web_portal is your own online portal; electronic_service is a third-party payoff or letter-of-guarantee service the insurer used. Case-insensitive.One of: phone, fax, email, mail, web_portal, electronic_service, other
client_namestring or nullFor a GAP administrator: the lender or dealer whose borrower this is. At most 120 characters.
requirement_basisRequirementBasisInputRequirementBasisInput or nullSend it only when the borrower's contract lets you require the review. It is recorded and checked against your program's bases; messages to the borrower still ask rather than require.

ReferralCreateResult

Every field of Referral, and:

Fields (1)
FieldTypeDescription
idempotent_replayrequiredbooleanTrue when this answers a replayed Idempotency-Key with the original referral.

ReferralDetail

A referral with the provider-facing milestone timeline and the financial panel (offers, settlement, exposure before and after, net savings, ROI).

Every field of Referral, and:

Fields (30)
FieldTypeDescription
consultation_numberrequiredstring or nullOur consultation number, once the borrower activates.
days_in_negotiationrequiredinteger or null
timelinerequiredobject or nullThe provider-facing milestone timeline. Null once reporting is withdrawn (status: reporting_withdrawn).
timeline.current_milestonerequiredstring or nullNew values may be added: show one you don't recognise as not yet known, and don't fail.One of: referral_submitted, borrower_invited, disclosure_attested, outreach_in_progress, borrower_activated, valuation_report_received, insurance_details_received, photos_received, market_research, preliminary_estimate_ready, appraisal_report_complete, authorization_received, insurer_notified, insurer_acknowledged, insurer_appraiser_appointed, appraisals_exchanged, umpire_engaged, award_letter_issued, settlement_finalized, outcome_recorded
timeline.terminalrequiredobject or null
timeline.terminal.statusrequiredstringNew values may be added: show one you don't recognise as not yet known, and don't fail.One of: submitted, invited, handoff_pending, outreach_queued, contact_attempted, activated, in_progress, settled, closed, declined, unreachable, expired, cancelled
timeline.terminal.labelrequiredstring
timeline.terminal.detailrequiredstring
timeline.phasesrequiredarray of object
timeline.phases[].keyrequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: referral, setup, research, negotiation, settlement
timeline.phases[].labelrequiredstring
timeline.phases[].staterequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: complete, current, upcoming, skipped
timeline.phases[].milestonesrequiredTimelineMilestonearray of TimelineMilestone
financialrequiredobject or nullThe financial panel. Null once reporting is withdrawn (status: reporting_withdrawn).
financial.initial_offer_centsrequiredinteger or null
financial.current_best_offer_centsrequiredinteger or null
financial.appraised_value_centsrequiredinteger or null
financial.final_settlement_centsrequiredinteger or null
financial.loan_payoff_centsrequiredinteger or nullNull for a key without referrals.contact:read.
financial.deductible_centsrequiredinteger or null
financial.fees_paid_centsrequiredinteger or null
financial.gross_uplift_centsrequiredinteger or null
financial.exposure_before_centsrequiredinteger or nullNull for a key without referrals.contact:read.
financial.exposure_after_centsrequiredinteger or nullNull for a key without referrals.contact:read.
financial.exposure_reduction_centsrequiredinteger or nullNull for a key without referrals.contact:read.
financial.net_savings_centsrequiredinteger or nullNull for a key without referrals.contact:read.
financial.roi_multiplerequirednumber or nullNull for a key without referrals.contact:read.
financial.figures_are_finalrequiredboolean
financial.savings_statementrequiredstring or nullNull for a key without referrals.contact:read.
financial.payoff_missingrequiredboolean

ReferralList

Fields (4)
FieldTypeDescription
objectrequired"list"
datarequiredReferralarray of Referral
has_morerequiredboolean
next_cursorrequiredstring or nullPass as starting_after for the next page; null on the last page. With updated_after it is never null: has_more says whether to fetch the next page now, and the last page's cursor is where your next poll resumes.

ReferralPatchInput

Either { "action": "cancel" } alone, or field edits.

One of ReferralCancelInput or ReferralUpdateInput.

ReferralProgramEditInput

New program terms for a referral, as on create, except that mode and subsidy_type are matched as sent: a value with spaces around it is refused. A mode without subsidy terms inherits your default subsidy only when it is split-pay.

Fields (3)
FieldTypeDescription
modestringWho pays for the appraisal. Case-insensitive. Matched as sent: a value with spaces around it is refused.One of: provider_paid, split_pay, customer_paid
subsidy_typestring or nullSplit-pay only: how subsidy_value reads. Case-insensitive. Matched as sent: a value with spaces around it is refused.One of: percent, fixed_cents
subsidy_valueinteger or nullSplit-pay only: the percent of the fee you cover (1 to 100), or the cents you cover, per subsidy_type. A split-pay referral needs both subsidy_type and a positive subsidy_value.

ReferralProgramInput

Per-referral program terms, overriding your program defaults. A mode without subsidy terms inherits your default subsidy only when it is split-pay.

Fields (3)
FieldTypeDescription
modestringWho pays for the appraisal. Case-insensitive.One of: provider_paid, split_pay, customer_paid
subsidy_typestring or nullSplit-pay only: how subsidy_value reads. Case-insensitive.One of: percent, fixed_cents
subsidy_valueinteger or nullSplit-pay only: the percent of the fee you cover (1 to 100), or the cents you cover, per subsidy_type. A split-pay referral needs both subsidy_type and a positive subsidy_value.

ReferralSimulateInput

One simulated step for a sandbox referral (test keys only).

Fields (4)
FieldTypeDescription
torequiredstringThe status to move the sandbox referral to; a step off its lifecycle is a 409 invalid_transition.One of: invited, activated, in_progress, settled, closed, declined, unreachable, expired
outcomeobjectWith to: "settled" only, and required there: the figures to settle with. The uplift and the exposure figures are computed from them as for a live settlement.
outcome.appraised_value_centsinteger
outcome.final_settlement_centsrequiredinteger

ReferralUpdateInput

Edit a referral. loan_payoff_cents and deductible_cents stay editable after activation; every other field locks when the borrower activates (409 referral_locked). Null or "" clears an optional field. Only spaces clears vehicle_make, vehicle_model, primary_carrier, claim_number and external_ref too, and is refused for the cents, the year, the email, the phone, the VIN and the loss and garaged states. The borrower's names can't be cleared, and program: null changes nothing.

Fields (25)
FieldTypeDescription
loan_payoff_centsinteger or nullEditable any time. Null or "" clears it.
deductible_centsinteger or nullEditable any time. Null or "" clears it.
borrower_first_namestringAt most 80 characters, as on create. It can't be cleared.
borrower_last_namestringAt most 80 characters, as on create. It can't be cleared.
borrower_emailstring or nullAn email address of at most 254 characters, with no spaces, a single @, and a domain with a dot that has text before it and at least two characters after it (example.com). Spaces around it are ignored. Stored in lowercase. At least one of borrower_email and borrower_phone is required. An invitation referral (the default consent_mode) needs an email while text invitations are off for its loss state: one without it is refused with 422 email_required_sms_disabled. A warm-handoff referral may be phone-only.
borrower_phonestring or nullA 10-digit US number; punctuation and a leading 1 are tolerated, and it is stored as 10 digits.
vinstring or null11 to 17 letters and digits, never I, O or Q. Case-insensitive; stored in uppercase.
vehicle_yearinteger or nullA model year from 1950 to two years after the current year.
vehicle_makestring or null
vehicle_modelstring or null
primary_carrierstring or null
claim_numberstring or null
loss_statestring or nullThe two-letter state of the loss, case-insensitive (tx is TX).
garaged_statestring or nullThe two-letter USPS code of the state where the vehicle is garaged (a state or DC), case-insensitive. We judge this state when it is sent, else loss_state: a referral for a state we don't serve may be refused with 422 state_not_served. Editable until the borrower activates; null or "" clears it.
external_refstring or nullYour own opaque reference, such as a case or file ID from your system; echoed in responses and webhooks. Never a loan, account or policy number (a credit union member number is an account number).
initial_offer_centsinteger or nullThe insurer's initial ACV offer, in cents.
programReferralProgramEditInputReferralProgramEditInput or nullNew program terms. Locked once the borrower activates under them (409 economics_locked); switching into provider-paid or split-pay needs a billing method (403 billing_required). Null is the same as leaving program out: the terms stay as they are.
claim_againststring or nullWhose insurer is handling the claim: own (the borrower's own insurer), other_driver (another driver's insurer), or unknown. Case-insensitive. Leave it out when you don't know. Editable until the borrower activates; null or "" clears it.One of: own, other_driver, unknown
cause_of_lossstring or nullThe cause of the loss, if you know it. weather is hail, wind or a storm; a flood is flood. Case-insensitive. Editable until the borrower activates; null or "" clears it.One of: collision, theft, fire, flood, weather, vandalism, animal, other
triggerstring or nullWhat led you to refer: payoff_request (an insurer asked you for the loan payoff), loss_notice (a notice of loss), gap_claim (the borrower filed a GAP claim), borrower_request (the borrower asked), or other. Case-insensitive. Editable until the borrower activates; null or "" clears it.One of: payoff_request, loss_notice, gap_claim, borrower_request, other
payoff_requested_atstring or nullWhen the insurer asked you for the loan payoff: a date (2026-06-16, read as midnight UTC) or an ISO 8601 timestamp with its UTC offset (2026-06-16T15:04:00-06:00). At most a day ahead. Editable until the borrower activates; null or "" clears it.
payoff_request_channelstring or nullHow that payoff request reached you. web_portal is your own online portal; electronic_service is a third-party payoff or letter-of-guarantee service the insurer used. Case-insensitive. Editable until the borrower activates; null or "" clears it.One of: phone, fax, email, mail, web_portal, electronic_service, other
client_namestring or nullFor a GAP administrator: the lender or dealer whose borrower this is. At most 120 characters. Editable until the borrower activates; null or "" clears it.
requirement_basisRequirementBasisInputRequirementBasisInput or nullSend it only when the borrower's contract lets you require the review. It is recorded and checked against your program's bases; messages to the borrower still ask rather than require. Editable until the borrower activates; null or "" clears it.
actionnullNull is the same as leaving action out. To cancel, send { "action": "cancel" } on its own.

RequirementBasisInput

The contract basis for requiring the review. Every member is required except filed_form_ref, which a carrier basis needs.

Fields (10)
FieldTypeDescription
basis_kindrequiredstringcontract: a provision in the borrower's own contract. carrier: a GAP insurance policy's form, filed in the state, which needs filed_form_ref. Case-insensitive.One of: contract, carrier
contract_form_idrequiredstringThe contract form's identifier, as the form names itself. 1 to 64 characters: letters, digits, spaces and . , _ - / ( ) # &. Spaces around it are ignored.
contract_form_versionrequiredstringThe form's version or edition: 1 to 64 characters, in the same characters as contract_form_id. Spaces around it are ignored.
contract_daterequiredstring (date)The date of the borrower's contract (YYYY-MM-DD); at most a day ahead. Spaces around it are ignored.
product_typerequiredstringThe product the contract is: a GAP waiver, GAP insurance, or a loan without GAP. Case-insensitive.One of: gap_waiver, gap_insurance, loan_without_gap
staterequiredstringThe two-letter USPS code of the state whose law the contract follows (a state or DC), case-insensitive.
a2_acknowledgedrequiredbooleanWhether the borrower acknowledged the signing disclosure in its own box (the program paper's Appendix A-2).
adopts_x5requiredbooleanWhether the contract adopts X.5 (no delay; no charges).
adopts_x6requiredbooleanWhether the contract adopts X.6 (no worse off).
filed_form_refstring or nullA carrier basis's filed form: its filing reference, as an identifier of at most 64 letters, digits, dots, hyphens and underscores. Never a URL. Spaces around it are ignored.

ReviewRecord

One version of a referral's review record. Versions only grow: one is written each time the record changes, never edited afterwards, and cited by { id, version, hash }.

Fields (9)
FieldTypeDescription
objectrequired"gap.review_record"
idrequiredstringThe version's id.
referral_idrequiredstringThe referral's id.
versionrequiredintegerThe version's number: 1 for the record's first version, and one more for each after it.
hashrequiredstringSHA-256, in lowercase hex, of bundle's canonical JSON (RFC 8785): hash the bundle you read to check it. A version never changes, and a redacted one keeps its hash.
statusrequiredstringThe review's status as of this version. PENDING: referred, and the borrower hasn't engaged yet. IN_REVIEW: the borrower activated, and no review has reached them. REVIEWED_NO_UNDERVALUATION: we told the borrower the offer looks fair. REVIEWED_UNDERVALUATION_FOUND: we told the borrower we can help. RESEARCH_DELIVERED: our research reached the borrower, with no verdict. REVIEWED_ELSEWHERE: the borrower had the review done elsewhere. DECLINED: the borrower declined. UNREACHABLE: we couldn't reach the borrower, or the invitation expired. RELEASED: the review window ended with nothing delivered. CANCELLED: you cancelled the referral before the borrower activated. REPORTING_WITHDRAWN: the borrower asked us to stop reporting this referral's progress. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: PENDING, IN_REVIEW, REVIEWED_NO_UNDERVALUATION, REVIEWED_UNDERVALUATION_FOUND, RESEARCH_DELIVERED, REVIEWED_ELSEWHERE, DECLINED, UNREACHABLE, RELEASED, CANCELLED, REPORTING_WITHDRAWN
created_atrequiredstring (date-time)When the version was written.
redactedrequiredbooleanTrue once the version's bundle has been removed: when its retention period ends, or when the borrower's data is erased at their request. Its id, version, hash and status stay.
bundlerequiredReviewRecordBundle or WithdrawnReviewRecordBundleReviewRecordBundle or WithdrawnReviewRecordBundle or nullThe record itself, or null when the version is redacted.

ReviewRecordBundle

One version of a referral's review record (review-record.v1). It names no one and carries no contact detail, VIN, claim number or dollar range, and it shows a verdict only once the verdict reached the borrower. Every date is ISO 8601 in UTC, and every amount integer cents.

Fields (67)
FieldTypeDescription
schemarequired"review-record.v1"The bundle's format.
referral_idrequiredstringThe referral's id.
statusrequiredstringThe review's status as of this version, as the record's status gives it. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: PENDING, IN_REVIEW, REVIEWED_NO_UNDERVALUATION, REVIEWED_UNDERVALUATION_FOUND, RESEARCH_DELIVERED, REVIEWED_ELSEWHERE, DECLINED, UNREACHABLE, RELEASED, CANCELLED
review_completed_atrequiredstring (date-time) or nullWhen the review completed: when our verdict, or our paid research, first reached the borrower, or when they had the review done elsewhere. Null until then; nothing after it moves it.
verdictrequiredobject
verdict.recommendationrequiredstring or nullThe verdict that reached the borrower. PROCEED: we told them we can help. DECLINE: we told them the offer looks fair. Null until one reaches them: you never hear a verdict before the borrower does. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: PROCEED, DECLINE
verdict.delivered_atrequiredstring (date-time) or nullWhen that verdict reached the borrower.
verdict.tierrequiredstring or nullThe review the verdict came from. PRELIMINARY: made before the valuation report, photos and receipts were all in. FULL: made with all of them. Null without a verdict. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: PRELIMINARY, FULL
verdict.decline_reasonrequiredstring or nullWith a DECLINE, why, when a reason was recorded: OFFER_FAIR, GAIN_BELOW_THRESHOLD, NO_APPRAISAL_RIGHT, EVIDENCE_INSUFFICIENT, OUT_OF_SCOPE or OTHER. Null otherwise.
verdict.research_onlyrequiredbooleanTrue when the borrower engaged us for research only, which carries no verdict.
verdict.research_delivered_atrequiredstring (date-time) or nullWhen our paid research reached the borrower.
verdict.full_fac_required_atrequiredstring (date-time) or nullWhen a full review became due: the borrower engaged us after a preliminary one.
verdict.full_fac_completed_atrequiredstring (date-time) or nullWhen that full review was completed.
verdict.re_review_requested_atrequiredstring (date-time) or nullWhen a change to the vehicle or the evidence sent the verdict back to be reviewed again.
materialityrequiredobject
materiality.rulerequiredstringThe rule we_can_help follows: D12 today. Other rules may be added: treat one you don't recognise as a rule you don't know, and read we_can_help as that rule's verdict.
materiality.we_can_helprequiredboolean or nullWhether we can help, under that rule: true when our valuation clears the insurer's offer by enough to be worth an appraisal, false when it doesn't. Never a dollar figure. Null when it couldn't be judged (no confirmed offer, or no valid valuation range), and until a verdict reaches the borrower.
materiality.second_review_pendingrequiredbooleanTrue while a second reviewer still has to check the verdict.
windowrequiredobject
window.valuation_received_atrequiredstring (date-time) or nullWhen the valuation report for the review came in.
window.ends_atrequiredstring (date-time) or nullWhen the review window ends: 11:59:59 PM Denver time on the earlier of 5 business days after the valuation report and 15 business days after the borrower was first told about the review. Null while no window runs.
releaserequiredobject
release.released_atrequiredstring (date-time) or nullWhen the borrower was released: the review window ended with nothing delivered. A review the borrower has started goes on.
release.notice_sent_atrequiredstring (date-time) or nullWhen the release notice went to the borrower.
outreachrequiredobject
outreach.consent_moderequiredstringHow the borrower came to us: INVITATION (we invited them) or WARM_HANDOFF (your staff handed them to us). New values may be added: treat one you don't recognise as unknown, and don't fail.One of: INVITATION, WARM_HANDOFF
outreach.invited_atrequiredstring (date-time) or nullWhen we sent the invitation.
outreach.disclosure_confirmed_atrequiredstring (date-time) or nullWhen your staff attested to the warm-handoff disclosure.
outreach.first_contact_atrequiredstring (date-time) or nullWhen we first contacted the borrower.
outreach.contact_attemptsrequiredintegerHow many times we tried to reach the borrower.
outreach.last_contact_attempt_atrequiredstring (date-time) or nullWhen we last tried.
outreach.reached_atrequiredstring (date-time) or nullWhen we reached the borrower.
outreach.activated_atrequiredstring (date-time) or nullWhen the borrower activated.
outreach.contact_stopped_atrequiredstring (date-time) or nullWhen the borrower asked us to stop contacting them about this referral.
refusalrequiredobject
refusal.recorded_atrequiredstring (date-time) or nullWhen the borrower's "no thanks" was recorded.
refusal.scoperequiredstring or nullWhat the refusal covers, as a code.
refusal.methodrequiredstring or nullHow the borrower gave it, as a code.
refusal.text_versionrequiredstring or nullThe version of the refusal text the borrower heard.
reviewed_elsewhere_atrequiredstring (date-time) or nullWhen we recorded that the borrower had the review done elsewhere.
appraisal_rightrequiredobject
appraisal_right.claim_againstrequiredstring or nullWhose insurer is handling the claim: own, other_driver or unknown, as you or the borrower told us.
appraisal_right.clause_invoked_atrequiredstring (date-time) or nullWhen the policy's appraisal clause was invoked with the insurer.
appraisal_right.clause_invoked_viarequiredstring or nullHow it was invoked, as a code.
costsrequiredobject
costs.engagementrequiredobject or nullThe engagement fee: who pays it, and each part. Null for a key without reporting:read.Null for a key without reporting:read.
costs.engagement.basisrequiredstringSNAPSHOT: the funding terms fixed when the borrower activated. LEGACY: the program's original prices, for a referral without them. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: SNAPSHOT, LEGACY
costs.engagement.payerrequiredstringWho pays the engagement fee: PARTNER (you), BORROWER, SPLIT (both), MEMBERSHIP (a membership covers it) or NONE (there is no fee). New values may be added: treat one you don't recognise as unknown, and don't fail.One of: PARTNER, BORROWER, SPLIT, MEMBERSHIP, NONE
costs.engagement.list_centsrequiredintegerThe engagement fee before anyone's part, in cents.
costs.engagement.institution_centsrequiredintegerYour part of it, in cents.
costs.engagement.borrower_centsrequiredintegerWhat the borrower is charged for it, in cents, after any discount.
costs.institution_chargesrequiredarray of object or nullWhat we have charged you on this referral, summed by kind and status. Null for a key without reporting:read.Null for a key without reporting:read.
costs.institution_charges[].kindrequiredstringThe charges' kind.
costs.institution_charges[].statusrequiredstringTheir status.
costs.institution_charges[].amount_centsrequiredintegerTheir total, in cents.
costs.institution_charges[].countrequiredintegerHow many there are.
requirementrequiredobject
requirement.moderequiredstringThe review requirement your program had when the borrower activated: REQUEST (also when none was set), REVIEW or REVIEW_AND_APPRAISAL. Messages to the borrower ask rather than require.
requirement.basis_attestedrequiredbooleanWhether you sent a requirement_basis with the referral.
requirement.basis_validated_atrequiredstring (date-time) or nullWhen that basis matched one of your program's approved bases.
outcomerequiredobject
outcome.settled_atrequiredstring (date-time) or nullWhen the borrower's claim settled.
outcome.closed_atrequiredstring (date-time) or nullWhen the referral's record closed.
outcome.close_reasonrequiredstring or nullWhy it closed: null while it is open. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: SETTLED, NO_MATERIAL_UNDERVALUATION, BORROWER_DECLINED, BORROWER_UNREACHABLE, INVITATION_EXPIRED, CANCELLED_BY_INSTITUTION, REVIEWED_ELSEWHERE, SETTLED_WITH_INSURER, OUT_OF_SCOPE, STATE_NOT_SERVED, NO_APPRAISAL_RIGHT, CLAIM_NOT_COVERED, NOT_COMPLETED, REPORTING_WITHDRAWN
copy_versionsrequiredobject
copy_versions.disclosure_scriptrequiredstring or nullThe version of the warm-handoff disclosure script your staff delivered.
copy_versions.refusal_textrequiredstring or nullThe version of the refusal text the borrower heard.

ReviewRecordReference

The latest version of the referral's review record, cited as the review webhooks cite one.

Fields (5)
FieldTypeDescription
idrequiredstringThe version's id.
versionrequiredintegerThe version's number: 1 for the record's first version, and one more for each after it.
hashrequiredstringSHA-256, in lowercase hex, of the version's canonical JSON (RFC 8785). A version never changes, so the version you read later still matches it.
statusrequiredstringThe review's status as of this version. PENDING: referred, and the borrower hasn't engaged yet. IN_REVIEW: the borrower activated, and no review has reached them. REVIEWED_NO_UNDERVALUATION: we told the borrower the offer looks fair. REVIEWED_UNDERVALUATION_FOUND: we told the borrower we can help. RESEARCH_DELIVERED: our research reached the borrower, with no verdict. REVIEWED_ELSEWHERE: the borrower had the review done elsewhere. DECLINED: the borrower declined. UNREACHABLE: we couldn't reach the borrower, or the invitation expired. RELEASED: the review window ended with nothing delivered. CANCELLED: you cancelled the referral before the borrower activated. REPORTING_WITHDRAWN: the borrower asked us to stop reporting this referral's progress. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: PENDING, IN_REVIEW, REVIEWED_NO_UNDERVALUATION, REVIEWED_UNDERVALUATION_FOUND, RESEARCH_DELIVERED, REVIEWED_ELSEWHERE, DECLINED, UNREACHABLE, RELEASED, CANCELLED, REPORTING_WITHDRAWN
created_atrequiredstring (date-time)When the version was written.

ReviewStatus

One of your referrals the lookup matched, and its review status: the version its record stands at.

Fields (6)
FieldTypeDescription
objectrequired"gap.review_status"
referralrequiredobject
referral.idrequiredstringThe referral's id.
referral.external_refrequiredstring or nullYour own reference, as you sent it.
recordrequiredReviewRecordReferenceReviewRecordReference or nullThe latest version of the referral's review record. Null while it has none to show: before its first version is written, and from the borrower's request that we stop reporting the referral's progress until the version that records it.
close_reasonrequiredstring or nullWhy the referral's record closed, as of that version: null while it is open, and whenever record is null. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: SETTLED, NO_MATERIAL_UNDERVALUATION, BORROWER_DECLINED, BORROWER_UNREACHABLE, INVITATION_EXPIRED, CANCELLED_BY_INSTITUTION, REVIEWED_ELSEWHERE, SETTLED_WITH_INSURER, OUT_OF_SCOPE, STATE_NOT_SERVED, NO_APPRAISAL_RIGHT, CLAIM_NOT_COVERED, NOT_COMPLETED, REPORTING_WITHDRAWN

ReviewStatusList

Your referrals the identifier matched, newest first, at most 100; an empty list when none did.

Fields (3)
FieldTypeDescription
objectrequired"list"
datarequiredReviewStatusarray of ReviewStatus
has_morerequiredbooleanTrue when more than 100 of your referrals matched: the newest 100 are listed.

ReviewStatusLookupInput

Exactly one identifier, matched against your referrals: vin, or claim_number or external_ref as you sent it on the referral, spaces around it aside. One that is blank or null is not sent.

Fields (3)
FieldTypeDescription
vinstring or nullThe vehicle's VIN: 11 to 17 letters and digits, never I, O or Q. Case-insensitive.
claim_numberstring or nullThe borrower's claim number with their insurer, as you sent it on the referral.
external_refstring or nullYour own reference for the referral, as you sent it.

SignatureCheckResult

What a test credential's signed request looked like to the API: whether it verified, and the base it was checked over.

Fields (4)
FieldTypeDescription
objectrequired"gap.signature_check"
verifiedrequiredbooleanWhether the request's signature verified with the credential's key over the signature base built from the request as received.
signature_basestringThe signature base built from the request as received (RFC 9421 section 2.5), to compare byte for byte with the one you signed.
codestringWhen verified is false: the code the same request would be refused with on any other operation. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: signature_profile_invalid, signature_expired, target_uri_not_allowed, signature_invalid, environment_mismatch

ThinEvent

One event, thin: what happened, to which record, and when. It is what the events feed lists and what a thin-payload webhook endpoint is sent (one created from the partner security platform's issue stage on), with the webhook's headers. New event types are added over time: a feed reader skips a type it doesn't recognise, and a webhook endpoint answers 2xx to a type it doesn't recognise, and ignores it.

Fields (9)
FieldTypeDescription
idrequiredstringThe event id (evt_…), the same id its webhook delivery carries: use it to drop duplicates across the feed and your webhooks. It is not a position: page with next_cursor.
objectrequired"event"
typerequiredstringNew values may be added: ignore a type you don't recognise.One of: referral.invited, referral.activated, referral.declined, consultation.status_changed, appraisal.completed, settlement.updated, charge.created, plan.enrollment.activated, plan.enrollment.past_due, plan.enrollment.cancelled, plan.benefit.redeemed, plan.benefit.completed, plan.statement.issued, review.completed, review.updated, referral.closed
api_versionrequired"1.1"The thin event's version: 1.1.
created_atrequiredstring (date-time)
livemodebooleantrue for a live event, false for a sandbox one; a membership plan event has its enrollment's. A membership statement and its charge don't carry it: a live key lists them, and they go to your live endpoints.
accountrequiredobjectThe institution the event belongs to.
account.idrequiredstringYour institution's id.
datarequiredobjectThe thin body, by type: the referral's id, status, your external_ref and its timestamps under referral, or the enrollment's under enrollment; the milestone's key, phase, previous and completed list; the codes and ids the event adds (channel, via, consultation_number, reason, redemption_id); a charge's id, status and statement_id; a statement's id, period, status and times; a review event's record (id, version, hash, status) and close_reason. Never a borrower's contact details, a payoff, a VIN or an amount: fetch the referral or enrollment for those. On the events feed, a key without referrals:read gets the referral as its id only, so a charge.created it lists names its referral but not your reference, its status or its timestamps; a thin-payload webhook endpoint is sent the whole thin body. After an erasure an event carries only the id and scrubbed: true.

TimelineMilestone

Fields (9)
FieldTypeDescription
keyrequiredstringNew values may be added: show one you don't recognise as not yet known, and don't fail.One of: referral_submitted, borrower_invited, disclosure_attested, outreach_in_progress, borrower_activated, valuation_report_received, insurance_details_received, photos_received, market_research, preliminary_estimate_ready, appraisal_report_complete, authorization_received, insurer_notified, insurer_acknowledged, insurer_appraiser_appointed, appraisals_exchanged, umpire_engaged, award_letter_issued, settlement_finalized, outcome_recorded
labelrequiredstring
detailrequiredstring
staterequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: complete, current, upcoming, skipped
atrequiredstring (date-time) or nullWhen the milestone completed, when known.
waiting_onrequiredstring or nullWho the current milestone waits on. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: borrower, secondappraisal, insurer
waiting_labelrequiredstring or null
delayedrequiredbooleanTrue when the current milestone has run past its expected time.
noterequiredstring or nullA third-person fact line, such as the number of contact attempts.

WebhookEvent

The full payload: the envelope a full-payload endpoint, one created before the partner security platform's issue stage, is sent; a thin-payload endpoint, created from issue on, is sent ThinEvent instead, with the same headers. Every delivery is a POST. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise. Verify X-SecondAppraisal-Signature: t=<unix seconds>,v1=<hex> first: v1 is HMAC-SHA256 with your endpoint's signing secret over <t>.<raw body>; compare in constant time and refuse an old t. The key is the secret's text for a whsec_gap_ secret, and the base64-decoded bytes after whsec_ for a Standard Webhooks secret, whose endpoint also gets webhook-id, webhook-timestamp and webhook-signature, which any Standard Webhooks library verifies. Answer any 2xx to acknowledge; anything else is retried with backoff. New event types are added over time: answer 2xx to a type you don't recognise, and ignore it.

Fields (6)
FieldTypeDescription
idrequiredstringThe event id (evt_…). A retried delivery repeats it: use it to drop duplicates.
objectrequired"event"
typerequiredstringNew values may be added: ignore a type you don't recognise.One of: referral.invited, referral.activated, referral.declined, consultation.status_changed, appraisal.completed, settlement.updated, charge.created, plan.enrollment.activated, plan.enrollment.past_due, plan.enrollment.cancelled, plan.benefit.redeemed, plan.benefit.completed, plan.statement.issued, review.completed, review.updated, referral.closed
created_atrequiredstring (date-time)
livemodebooleantrue for a live event, false for a sandbox one (from a test key's referral). Live events go to your live endpoints only, and sandbox referral events to your test endpoints only. The membership plan events don't carry it yet.
datarequiredobjectDepends on type.

WebhookReferral

A referral as a webhook carries it. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise. The API's referral carries more: fetch it to read every field. Once the borrower withdraws permission to report the referral's progress, it carries status: reporting_withdrawn and no progress, as every read shows it.

Fields (44)
FieldTypeDescription
idrequiredstringThe referral's id.
objectrequired"gap.referral"
statusrequiredstringWhere the referral stands. reporting_withdrawn once the borrower withdrew permission to report its progress to you, whatever happens to it after: from then on it shows the fields you supplied and no progress. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: submitted, invited, handoff_pending, outreach_queued, contact_attempted, activated, in_progress, settled, closed, declined, unreachable, expired, cancelled, reporting_withdrawn
status_labelrequiredstringThe status as the portal shows it.
status_tonerequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: green, yellow, red, neutral
status_noterequiredstring or nullA third-person note on a terminal status, such as why it closed.
external_refrequiredstring or nullYour own reference, as you sent it.
borrowerrequiredobject
borrower.first_namerequiredstring
borrower.last_namerequiredstring
borrower.emailrequiredstring or null
borrower.phonerequiredstring or null10 digits.
vehiclerequiredobject
vehicle.vinrequiredstring or null
vehicle.yearrequiredinteger or null
vehicle.makerequiredstring or null
vehicle.modelrequiredstring or null
claimrequiredobject
claim.carrierrequiredstring or null
claim.claim_numberrequiredstring or null
claim.loss_staterequiredstring or null
claim.date_of_lossrequiredstring (date-time) or nullMidnight UTC on the date of loss.
claim.initial_offer_centsrequiredinteger or null
liabilityrequiredobject
liability.loan_payoff_centsrequiredinteger or null
liability.deductible_centsrequiredinteger or null
programrequiredobject
program.moderequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: provider_paid, split_pay, customer_paid, membership
program.subsidy_typerequiredstring or nullNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: percent, fixed_cents
program.subsidy_valuerequiredinteger or null
program.price_centsrequiredintegerYour price for this referral's consultation, in cents, fixed when the referral was submitted. mode and the subsidy terms decide how much of it you pay.
program.lockedrequiredboolean or nullTrue once the borrower activated under these terms; they can no longer change. Null once reporting is withdrawn (status: reporting_withdrawn).
consentrequiredobject
consent.moderequiredstringNew values may be added: treat one you don't recognise as unknown, and don't fail.One of: invitation, warm_handoff
consent.disclosure_confirmed_atrequiredstring (date-time) or null
consent.disclosure_channelrequiredstring or null
consent.disclosure_attestor_namerequiredstring or null
submitted_viarequiredstringWhere the referral came from: api, dashboard or csv.
created_atrequiredstring (date-time)
invited_atrequiredstring (date-time) or null
invitation_expires_atrequiredstring (date-time) or null
activated_atrequiredstring (date-time) or null
settled_atrequiredstring (date-time) or null
closed_atrequiredstring (date-time) or null

WebhookReferralActivatedData

The referral, its consultation number, and how the borrower activated. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise.

Fields (3)
FieldTypeDescription
referralrequiredWebhookReferral
consultation_numberrequiredstring or nullOur consultation number for the borrower's case.
viarequiredstringWhether the borrower activated through the link or with our team's help. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: borrower_link, admin_assisted

WebhookReferralData

The referral, as webhooks carry it. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise.

Fields (1)
FieldTypeDescription
referralrequiredWebhookReferral

WebhookReferralInvitedData

The referral, and how its invitation went out. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise.

Fields (2)
FieldTypeDescription
referralrequiredWebhookReferral
channelrequiredstringHow the invitation went out. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: email, sms

WebhookReviewRecord

The version of the referral's review record that the event reports.

Fields (4)
FieldTypeDescription
idrequiredstringThe version's id.
versionrequiredintegerThe version's number: 1 for the record's first version, and one more for each after it.
hashrequiredstringSHA-256, in lowercase hex, of the version's canonical JSON (RFC 8785). A version never changes, so the version you read later still matches it.
statusrequiredstringThe review's status as of this version. PENDING: referred, and the borrower hasn't engaged yet. IN_REVIEW: the borrower activated, and no review has reached them. REVIEWED_NO_UNDERVALUATION: we told the borrower the offer looks fair. REVIEWED_UNDERVALUATION_FOUND: we told the borrower we can help. RESEARCH_DELIVERED: our research reached the borrower, with no verdict. REVIEWED_ELSEWHERE: the borrower had the review done elsewhere. DECLINED: the borrower declined. UNREACHABLE: we couldn't reach the borrower, or the invitation expired. RELEASED: the review window ended with nothing delivered. CANCELLED: you cancelled the referral before the borrower activated. REPORTING_WITHDRAWN: the borrower asked us to stop reporting this referral's progress. New values may be added: show one you don't recognise as not yet known, and don't fail.One of: PENDING, IN_REVIEW, REVIEWED_NO_UNDERVALUATION, REVIEWED_UNDERVALUATION_FOUND, RESEARCH_DELIVERED, REVIEWED_ELSEWHERE, DECLINED, UNREACHABLE, RELEASED, CANCELLED, REPORTING_WITHDRAWN

WebhookReviewRecordData

The referral, and the version of its review record that the event reports. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise. A version that completes the review and closes the referral sends both review.completed and referral.closed. Staged: none of these events is sent until review records open.

Fields (5)
FieldTypeDescription
referralrequiredobject
referral.idrequiredstringThe referral's id.
referral.external_refrequiredstring or nullYour own reference, as you sent it.
recordrequiredWebhookReviewRecord
close_reasonrequiredstring or nullWhy the referral's record closed: null while it is open, and again if it reopens. New values may be added: treat one you don't recognise as unknown, and don't fail.One of: SETTLED, NO_MATERIAL_UNDERVALUATION, BORROWER_DECLINED, BORROWER_UNREACHABLE, INVITATION_EXPIRED, CANCELLED_BY_INSTITUTION, REVIEWED_ELSEWHERE, SETTLED_WITH_INSURER, OUT_OF_SCOPE, STATE_NOT_SERVED, NO_APPRAISAL_RIGHT, CLAIM_NOT_COVERED, NOT_COMPLETED, REPORTING_WITHDRAWN

WebhookSettlementData

The referral, and the outcome its settlement recorded. This object and each object in it carry at least these fields; new fields may be added, so ignore any you don't recognise.

Fields (10)
FieldTypeDescription
referralrequiredWebhookReferral
outcomerequiredobject
outcome.initial_acv_centsrequiredinteger or null
outcome.appraised_value_centsrequiredinteger or null
outcome.final_settlement_centsrequiredinteger or null
outcome.uplift_centsrequiredinteger or null
outcome.exposure_before_centsrequiredinteger or null
outcome.exposure_after_centsrequiredinteger or null
outcome.fees_paid_centsrequiredinteger or null
outcome.settled_atrequiredstring (date-time) or null

WithdrawnReviewRecordBundle

A version written after the borrower asked us to stop reporting the referral's progress: it says only that, and when. The versions before it stay readable by number.

Fields (5)
FieldTypeDescription
schemarequired"review-record.v1"The bundle's format.
referral_idrequiredstringThe referral's id.
statusrequired"REPORTING_WITHDRAWN"
close_reasonrequired"REPORTING_WITHDRAWN"
reporting_withdrawn_atrequiredstring (date-time)When the borrower asked us to stop reporting this referral's progress to you.

Data classes

Each operation states the most sensitive class of data it carries.

public
A value with no meaning outside the API: a status name, a label, a count, an object type, or a timestamp of a public event.
institution_confidential
The institution's own business records with SecondAppraisal: its references, record ids, program terms, prices and fees, statements and aggregate reports.
borrower_personal
Facts about one borrower or member: their name, vehicle and VIN, insurance carrier and claim number, loss state and date, and the amounts of their loss, loan and settlement.
borrower_contact
How to reach one borrower or member: an email address or a phone number.
credential
A value that grants access by itself, such as a member's activation link.
The document, from the command line
curl https://secondappraisal.com/api/gap/v1/openapi