Skip to content
Select theme

Guarantees

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.


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.

  • 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.

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.

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.

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.

  • 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.

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.


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

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.

Payer Routing NumberScenarioHTTP Status
000000001Approved200
000000002Declined200
999999999Decisioning temporarily unavailable503
Any other valueFalls through to live sandbox scoringlive

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

Payer Routing NumberdecisionGuarantee created
000000001guaranteeYes
000000002no_guaranteeYes
999999999none returnedNo

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.


If you’re encountering any issues, please reach out to support@grailpay.com.


Maintained by EkLine