# Guarantees

:::note
Guarantees are not enabled by default and require an approved account. If you would like to offer this
functionality, contact your GrailPay integration support contact to have it enabled on your account.
:::

Guarantees are part of GrailPay's risk evaluation system. They build on the same underlying risk signals as
Transaction Intelligence, but rather than returning a score for you to threshold yourself, they return a decision.

---

## Accounts Receivable Guarantees

An accounts receivable guarantee applies to funds being collected by a business. The payee is a verified business
entity—one whose organization details, tax identifier, and beneficial owners have been supplied and evaluated—and the
payer is the party being debited to satisfy what that business is owed. The payer may be an individual or an
organization.

Because the payee is a known and verified business, the risk in a receivable sits almost entirely on the payer side.
This is why the request asks for considerably more about the payer than their bank account alone. Alongside identity
and location, you supply behavioral context: when the payer's account was opened on your platform, how much they have
spent, how many transactions they have completed, whether this is their first, and the device and channel they are
transacting from. GrailPay holds none of this history—it exists only on your platform—and it is what distinguishes an
established customer from one who appeared this morning.

To request a guarantee, send a POST request with details about the payer, the payee, and the transaction. GrailPay
evaluates both parties against its risk signals and returns:

- **Guarantee ID** – A UUID identifying the guarantee record created for this request. Retain this value; it is the
identifier used to cancel the guarantee.
- **Decision** – Either `guarantee` or `no_guarantee`.

Transaction details describe the debit itself: the amount, the SEC code under which it will be originated, the date it
was initiated, and the processing speed.

For full request and response examples, see the [Guarantee API reference](/api/risk/#tag/guarantee/POST/api/v3/guarantees/receivable).

### Usage Recommendations

- Send amounts as integers in cents. A transaction at or above `1000000` cents ($10,000) is declined automatically and
returns `no_guarantee`.
- Send exactly one of `payer.individual` or `payer.organization`. Name, email, and phone belong to whichever identity
object you send rather than to the payer itself.
- Send all timestamps in UTC. The only date field that is not a timestamp is a beneficial owner's `dob`, which is a
calendar date.
- Use `tax_id` for every tax identifier—individual, organization, and beneficial owner alike.
- Populate the behavioral fields on the payer as completely and accurately as possible. These describe the payer's
history on your platform and are not available to GrailPay from any other source.
- Store the returned guarantee ID against your own record of the transaction so that a cancellation can be issued
without a lookup.

---

## Guarantee Feedback

A guarantee is a decision made before a debit runs. Feedback is how you tell us what happened afterward. Did you originate the debit? Did you rely on the guarantee for it? Did it settle, come back as a return, or never leave the gate? Each of those is a moment in the transaction's life, and you report each one as it happens, against the guarantee ID you were given at creation. A single guarantee usually accumulates a few feedback events over its life—originated first, then settled or returned—and the record updates as each one arrives.

### Sending an event

Feedback is a POST against the guarantee ID. Each event carries its own `X-Idempotency-Key` header—up to 128 characters of letters, digits, underscores, and hyphens—and that key is what makes it a distinct event. Send a new key and you've recorded a new stage of the lifecycle. Send one you've used before and you'll get a conflict, which is exactly what you want when a retry crosses paths with a request that already landed.

The body answers two questions: what you did, and what happened.

What you did is `client_originated` (did the debit go out?) and `client_guarantee_applied` (did you rely on the guarantee for it?). You can only apply a guarantee to a debit you originated, and only when the decision was `guarantee`. These two values are set by your first event and must stay the same on every event after it. Changing either is treated as a conflict, because it would mean rewriting history rather than adding to it.

What happened is `outcome_status`—one of `not_originated`, `originated`, `settled`, `returned`, `cancelled`, or `failed_processing`—along with `outcome_observed_at`, the time you saw it, as an ISO 8601 timestamp with an offset or `Z`. The outcome has to agree with what you originated: a debit you sent can't be `not_originated`, and a debit you didn't send can only be `not_originated`, `cancelled`, or `failed_processing`.

Two outcomes need a little more. A `returned` status also requires `return_code` and `returned_at`. And if a notification of change came back on the debit, set `noc_received` to `true` and include `noc_code`, `noc_received_at`, and `noc_reason`. Leave all of these out when they don't apply.

Include your own `client_reference_id` on every event so the outcome lands against the right transaction on your side without a lookup.

### What comes back

The response echoes the guarantee ID and your reference, tells you whether this was the first feedback for the guarantee (`recorded`) or a later one (`updated`), and stamps the time it was received.

If something's wrong, the status code tells you what kind of wrong. A `404` means the guarantee ID doesn't exist. A `409` is a conflict: the idempotency key has already been used, or `client_originated` or `client_guarantee_applied` differs from an earlier event. A `422` is a validation failure—a missing or malformed key, a required field absent, or `client_guarantee_applied` set to `true` when the decision wasn't `guarantee`. A `503` means the feedback service is briefly unavailable; keep the same key and try again shortly.

For full request and response examples, see the [Guarantee Feedback API reference](/api/risk/#tag/guarantee/POST/api/v3/guarantees/{guarantee_id}/feedback).

### Usage Recommendations

- Mint a fresh `X-Idempotency-Key` for each lifecycle event and hold onto it until you've seen a response. If the request fails first, retry with the same key: a `409` tells you it already landed, rather than quietly recording a duplicate.
- Report each stage as you learn it rather than waiting for the final outcome. Later events update the record, so nothing is lost by sending `originated` before you know how it settled.
- Settle `client_originated` and `client_guarantee_applied` at the first event and don't revisit them. Every later event has to carry the same values.
- Send timestamps in UTC, as everywhere else in this API.
- Use the same `client_reference_id` you hold against the transaction so outcomes reconcile on your side without a lookup.

---

## Cancel Guarantees

Not every guarantee that gets issued ends up being wanted. Sometimes the transaction itself falls away—a customer
abandons a checkout, an order is voided, a duplicate submission is caught downstream. Other times the transaction
proceeds exactly as planned and you simply decide you do not need it guaranteed: the payer turns out to be a
long-standing customer, the amount is small enough to absorb, or your own review clears it. Either way, the guarantee
is no longer serving a purpose and should be released rather than left standing.

A guarantee can be cancelled for 15 minutes, measured from the time the original request was received rather than from
when your system read the response. Cancellation is performed against the guarantee ID returned at creation, not
against a transaction or request identifier—which is why that value is worth persisting alongside your own record of
the transaction at the moment you receive it.

For full request and response examples, see the [Cancel Guarantee API reference](/api/risk/#tag/guarantee/POST/api/v3/guarantees/{guarantee_id}/cancel).

---

## Sandbox Testing

The sandbox reserves a small set of routing numbers that return a fixed decision. Use them to build and test against
each outcome so you can see how real results will behave, without depending on live decisioning to produce a
particular answer.

Sandbox base URL: `https://risk-api-sandbox.grailpay.com`

### Selection Is Driven by the Payer

Scenarios are selected by `payer.bank_account.routing_number` alone. The payee's routing number is not evaluated for
this purpose, and account numbers are not reserved—any value that passes standard validation will do.

Your request is validated in full before a scenario is selected. An invalid payload is rejected on its own terms, so a
reserved routing number will not rescue a request that is missing required fields, sends both `payer.individual` and
`payer.organization`, or violates any other rule.

### Test Routing Numbers

| Payer Routing Number | Scenario                              | HTTP Status |
|----------------------|---------------------------------------|-------------|
| `000000001`          | Approved                              | `200`       |
| `000000002`          | Declined                              | `200`       |
| `999999999`          | Decisioning temporarily unavailable   | `503`       |
| Any other value      | Falls through to live sandbox scoring | live        |

### What Each Routing Number Returns

For calls to `POST /api/v3/guarantees/receivable`, each reserved routing number returns the following:

| Payer Routing Number | `decision`      | Guarantee created |
|----------------------|-----------------|-------------------|
| `000000001`          | `guarantee`     | Yes               |
| `000000002`          | `no_guarantee`  | Yes               |
| `999999999`          | none returned   | No                |

Both `200` scenarios create a real guarantee record, so the returned ID behaves exactly as it would in production—you
can use `000000001` to test your cancellation flow end to end. The `503` scenario returns a message indicating that
guarantee decisioning is experiencing a disruption and should be retried later; no guarantee is created, so there is
nothing to cancel.

:::note
These routing numbers are reserved in the sandbox only. In production they are treated as ordinary routing numbers
and are scored live.
:::

---

## Questions?

If you're encountering any issues, please reach out to [support@grailpay.com](mailto:support@grailpay.com).

---