Skip to content

Lock a price against a real recipient.

POST

Path parameters

  • orgIdstringRequired

    The opaque organization id issued to you, normally CUID-shaped (for example cmsx…). It is not an org_-prefixed alias. Pass it unchanged in every organization-scoped path.

Headers

  • Idempotency-KeystringOptional

    Same semantics as Idempotency-Key on the money routes (replay on the same body, 409 on a different one, released by a 4xx), but optional here: without it the request simply runs once with no replay. Every POST, PATCH and DELETE honors it; send one whenever your client might retry.

Body

This endpoint expects a JSON object.

  • amountstringRequired

    Decimal string with at most six fractional digits. Its currency is selected by amountLeg.

  • destinationAccountIdstringRequired

    From the recipient's paymentMethods[].destinationAccountId.

  • purposeOfPaymentstringOptional

    Corridor-defined payment purpose, from GET /payment-reasons. Required for payouts to INR, GHS, CNY and BRL (400 VALIDATION_ERROR naming the field, before anything is priced); validated against the catalog whenever you send it; never defaulted. Do not invent a default for regulated payments.

  • amountLegstringOptionalDefaults to "source"

    source prices the amount to debit. destination locks the amount the recipient receives in their currency. source_net starts with an amount in your source currency, converts it at the published market rate, and locks that destination amount. The two exact-output modes require capabilities.exactOutput: true from the corridors call.

    Allowed values:sourcedestinationsource_net

Behavior

Returns a snapshot with the locked rate and the amount the recipient receives. No money moves. Your caps and screening run here too, so an over-cap amount or a refused recipient fails before a quote is spent (422).

Where an underfunded balance fails depends on the environment. The sandbox checks it here (400 BAD_REQUEST, "Insufficient USD balance … Top up with POST …/sandbox/fund"). Live, the balance is held when you accept the quote, so 400 INSUFFICIENT_BALANCE comes from POST /quotes/accept or POST /payouts.

Quotes expire. Price and send close together.

Responses

200A locked quote snapshot

Body · QuoteSnapshot

  • idstringOptional

    Pass as snapshotId when sending.

  • best_quote_idstringOptional

    Pass as quoteId when sending.

  • expires_atstring<date-time>Optional
  • quotesarray of objectOptional
    Show 6 properties
    • idstringOptional
    • inobjectOptional
      Show 2 properties
      • amountstringOptional
      • currencystringOptional
    • outobjectOptional
      Show 2 properties
      • amountstringOptional
      • currencystringOptional
    • feesobjectOptional
      Show 2 properties
      • totalstringOptional
      • currencystringOptional
    • ratestringOptional

      The rail's own indicative rate, passed through for display. Its direction and whether the fee is inside it differ by rail, so do not expect out = (in - fees.total) x rate to hold. Reconcile on in and out; Payout.rate is the one derived from the amounts.

    • expires_atstring<date-time>Optional

Errors

  • 400

    Nothing was sent.

    • VALIDATION_ERROR: a field is malformed; errors names it.
    • QUOTE_NOT_POSITIVE: fees would leave the recipient with zero or less. Increase the amount and re-quote.
    • EXACT_OUTPUT_UNSUPPORTED: amountLeg: destination or source_net on a routing without capabilities.exactOutput.
    • INDICATIVE_PRICING_UNAVAILABLE: amountLeg: source_net on a routing that publishes no market rate. Send amountLeg: destination instead.
    • BAD_REQUEST: an unsupported corridor, an amount outside the corridor's minimum or maximum, or (sandbox only) an underfunded balance; detail says which.
    • IDEMPOTENCY_KEY_INVALID: a malformed Idempotency-Key.
  • 401

    The key was refused. Nothing ran.

    • UNAUTHORIZED: missing, invalid or revoked, or a key on a route that does not accept one.
    • KEY_EXPIRED: the key passed the expiry it was issued with. Issue a new one; an expired key cannot be rotated.
    • KEY_IP_NOT_ALLOWED: the key is pinned to source addresses and this request came from another.
  • 403

    USE_POST_PAYOUTS: this organization has an approval threshold or a velocity cap, and only POST /payouts enforces them. Nothing was sent; send the payout through POST /payouts, which may answer 202 pending_approval. Also the refusals every write can answer: FORBIDDEN (a key for a different organization), ACCOUNT_BLOCKED, LIVE_KEY_ORG_NOT_APPROVED and INSUFFICIENT_SCOPE.

  • 404

    DESTINATION_ACCOUNT_NOT_FOUND: no payment method with this destinationAccountId belongs to your organization. Nothing was sent. Use a destinationAccountId from a recipient you created.

  • 422

    A control refused it and nothing was sent. PAYOUT_LIMIT_EXCEEDED: over a single, daily, or per-end-user daily cap. The message names which; split it or ask us to raise the cap. PAYOUT_REFUSED: this payout cannot be sent to this recipient; do not retry, contact us with the requestId.

  • 429

    Too many requests. The default ceiling is 100 requests per minute per API credential on a 60-second window. High-volume payout and reconciliation routes declare a 600/minute override, and batch submission a 30/minute ceiling. A separate 2,000/minute per-source-IP abuse ceiling always applies.

    Obey Retry-After; it is in seconds and is authoritative. A 429 means the request was refused before the handler ran. Retry reads normally; retry an idempotent mutation with its same Idempotency-Key.

Error body · Error
  • typestringRequired

    Stable machine-readable code.

  • detailstringRequired

    What went wrong, in a sentence. Always a string, so detail.toLowerCase() is safe.

    More

    This is the field to read on BAD_REQUEST and PROVIDER_REJECTED, where the type alone does not name the condition.

  • messagestringRequired

    The same text as detail, kept for integrations written before detail existed. Read detail.

  • resolutionstringOptional

    What to do about it, when there is a specific answer. It is not on every error (it is absent on BAD_REQUEST, NOT_FOUND, PAYOUT_NOT_CANCELABLE and DESTINATION_ACCOUNT_NOT_FOUND), so treat it as optional and fall back to detail.

  • statusintegerRequired

    HTTP status, repeated in the body.

  • statusCodeintegerRequired

    The same value as status, kept for integrations written before status existed. Read status.

  • requestIdstringRequired

    Quote this to support and we can find the exact request. Also sent as the x-request-id response header, which is the only place it appears on a successful response. Success bodies do not carry it. Send your own x-request-id on the request and we use it, so your trace and ours share one identifier; otherwise we mint one.

  • errorsarray of stringOptional

    Present on VALIDATION_ERROR; names each field that failed.

  • originalIdempotencyKeystringOptional

    On DUPLICATE_REQUEST_DETECTED only. Send the request again with this to receive the original payout instead of making a second one. Without it there is no way to recover except by risking a double payment.

  • originalPayoutIdstringOptional

    On DUPLICATE_REQUEST_DETECTED only. The payout the first request created.

  • originalBatchIdstringOptional

    On a batch DUPLICATE_REQUEST_DETECTED. The run the first request created.

  • originalRequestIdstringOptional

    On a 409 PAYOUT_OUTCOME_UNKNOWN replay. The requestId of the call whose outcome is unknown; quote it to support.

  • existingRecipientIdstringOptional

    On BANK_ACCOUNT_ALREADY_LINKED. The recipient in your organization that already holds this account.

  • existingMethodIdstringOptional

    On BANK_ACCOUNT_ALREADY_LINKED. The payment method on that recipient.

Branch on type, never on the status or the message. Every error type is listed with what to do about it.

Avvio Partner Payouts · Quotes & rates · operation pricePayout

Try it: Quote a payout

POST https://api.avvio.xyz/business/api/v1/payments/organizations/{orgId}/quotes/offramp

Test keys only. Sent as x-api-key through this site's proxy to the Avvio API, never saved, and cleared when you close this dialog.

Was this page helpful?