---
title: "Schedule a Payee Payment"
method: POST
path: "/payables/payer_accounts/{payer_id}/payee_payments"
tags: ["Payee Payments"]
---

# Schedule a Payee Payment

`POST /payables/payer_accounts/{payer_id}/payee_payments`

## Request

- Base URL: `https://api.justifi.ai/v1`
- URL: `https://api.justifi.ai/v1/payables/payer_accounts/{payer_id}/payee_payments`
- Auth: none declared. Required headers: `Authorization`, `Idempotency-Key`
- TypeScript: <https://skmtc.dev/justifi/apis/justifi-api-documentation/docs/payables/payer_accounts/:payer_id/payee_payments/post.ts> (a function with typed inputs; its response is checked against the schema)

Schedule a payment to a payee. This debit-pulls the gross `amount` from the payer's funding bank
account, holds it, then credits the payee's receiving account with `amount` minus `fees`. The `fees`
array is required (no default) and is carved out of `amount`. There is no automatic retry: a returned
debit-pull fails the payment, and a returned credit to the payee refunds the payer.

**You name the payee; everything else about the routing is resolved for you.** The funding account
comes from the payer account in the path and the receiving account from the payee, so neither is a
request field — a payment can only ever move money between the two accounts those records already
point at. The schedule is set by the system: each leg takes three banking days, and `debits_at` and
`deposits_at` are returned once known. `payment_type` and `currency` are optional and default to
`ach` and `usd`, the only values either accepts.

**The payer account and the payee must both be `active`**, and either one that is not is a `400`
rather than a `422` — a status is the state of a record the request refers to, not a problem with
the body. A payee that is `pending`, `disabled` or `archived` cannot be paid.

Both accounts must be usable, and the bar differs by side. The payer account's funding account must be
**verified**. The payee's receiving account does **not** need to be verified — `not_required` is the
normal state for one — but it must **exist** and must not have **failed** verification: a payee can be
created without a bank account, and naming one that has none leaves the payment with nothing to credit.
Either failure is a `422`.

## Path parameters

- `payer_id` string, required

## Headers

- `Authorization` string, required
- `Idempotency-Key` string, required

## Request body

- object
  - `amount` integer, required: the gross amount debited from the payer, in cents. The payee receives `amount` minus the sum of `fees` (pass 91000 with a 1000 fee to net the payee 90000).
  - `currency` 'usd': defaults to `usd`, the only supported currency
  - `payee_id` string, required
  - `fees` object[], required: fees to charge on this payment (v2 fee convention), carved out of `amount`. **Required, no default** — the platform sets the fee explicitly.
    - `type` 'processing_fee', required: the fee type (v2 convention): - `processing_fee` — payment-processing cost, passed by platform
    - `amount` integer, required: fee amount in cents
  - `payment_type` 'ach': the rail the payment moves over. **Optional, and defaults to `ach`** — ACH is the only rail Payables moves money over, so there is nothing to select.
  - `description` string
  - `metadata` object

## Response `200`

This `Idempotency-Key` already scheduled a payment for this payer account, and the request body
matches. The payment is returned **as it stands now** — not a cached copy of the first response —
so it may have advanced past `initiated`. Nothing was scheduled a second time.

- object: the standard JustiFi response envelope for a single record
  - `id` string, required: the object id, also found in the data object
  - `type` string, required: the object type
  - `data` object, required: A payee payment moves money from the account's funding bank account to a payee's receiving account over ACH. It orchestrates an inbound debit-pull, a hold, and an outbound credit, accruing fees along the way. There is no automatic retry, and a payment cannot be cancelled once initiated. A return ends the payment either at `failed` or, when the payer has already been debited, at `refunding_payer` — see `status`.
    - `id` string: unique payee payment id
    - `type` string: the object type, matching the enclosing envelope's `type`
    - `payer_account_id` string: the id of the payer account this payment is scoped to
    - `amount` integer: the gross amount debited from the payer, in cents. The payee receives `amount` minus the sum of `fees` (e.g. `amount` 91000 with a 1000 fee pays the payee 90000).
    - `currency` 'usd'
    - `status` 'initiated' | 'inbound_submitted' | 'holding' | 'outbound_submitted' | 'succeeded' | 'failed' | 'refunding_payer' | 'refunded' | 'failed_late_return': the payment's position in its lifecycle. `initiated` → `inbound_submitted` → `holding` → `outbound_submitted` → `succeeded` is the happy path. **`holding` is a real wait.** Once the payer's funding has settled, the payee's credit is held until the next banking day before it is submitted, so a payment rests in `holding` rather than passing through it. Read `deposits_at` for when the payee is expected to be deposited; the hold is already in that estimate. **The hold only lifts for an active payer account and an active payee.** When the wait is up, the payee's credit is sent if both are `active`; if either is not, the payment stays in `holding` and is reconsidered periodically, so it pays out once both are active again. A hold that persists pushes the deposit past the `deposits_at` estimate, which is why that field is an estimate. Which return path a payment takes depends on whose money was already moved. A returned **inbound** debit-pull means nothing settled, so the payment is `failed` and no one is owed anything. A returned **outbound** credit means the payer was already debited, so the payment moves to `refunding_payer` and then `refunded` once the payer has their money back. Both `failed` and `refunded` are terminal. **`succeeded` is not the end.** ACH lets a return arrive days after an entry settled, so one can land after a payment has succeeded. That moves the payment to `failed_late_return`, which is terminal — JustiFi works the break by hand and records the corrective movement as a further leg on the same payment. See "A return that arrives after a payment succeeded" in the overview. **`failed_late_return` does not mean the payee holds nothing.** It reports that the payment did not stick, and the two ways that happens are opposites: a returned **outbound** credit means the payee never kept the money, while a returned **inbound** debit-pull means the payer's funding was clawed back *after* the payee was paid — so the payee still has it. **Re-sending on this status can pay a payee twice.** Read the payment's `transfers` to see which leg returned before acting.
    - `payment_type` 'ach': how the funds move. ACH is the only option. Named `payment_type` rather than `payment_method`, which in the JustiFi API denotes a stored instrument object, not a rail.
    - `payee_id` string: the payee being paid
    - `funding_bank_account_id` string: the account's funding bank account the principal is debit-pulled from
    - `receiving_bank_account_id` string: the payee's receiving account the principal is credited to
    - `debits_at` string, date-time, nullable: in UTC, the estimated date and time the payer's funding account is debited (from the inbound leg); null until scheduled. Normally three banking days after the payment is submitted.
    - `deposits_at` string, date-time, nullable: in UTC, the estimated date and time the payee is deposited (from the outbound leg); null until scheduled. Each ACH leg takes three banking days and the payee's credit is held for one banking day after the payer's funding settles, so this is normally four banking days after `debits_at`. Estimated, and may shift if the provider revises an effective entry date.
    - `description` string, nullable
    - `fees` object[]: the fees charged on this payment (v2 fee convention), carved out of `amount`. Supplied in the create request (required, no default) and echoed here. Fees incurred later by NOCs or returns are not shown here — they are billed to the platform monthly.
      - `type` 'processing_fee', required: the fee type (v2 convention): - `processing_fee` — payment-processing cost, passed by platform
      - `amount` integer, required: fee amount in cents
    - `transfers` object[]: the ACH legs that make up this payment
      - `id` string: unique transfer leg id
      - `direction` 'inbound' | 'outbound': the direction of funds for this leg (a refund is an outbound, a recovery an inbound)
      - `purpose` 'principal' | 'refund' | 'recovery': what this leg is — moves the principal, refunds the payer, or recovers owed funds from the payer
      - `transfer_type` 'ach' | 'manual': how the money moved. `ach` — over the ACH network; `manual` — a movement JustiFi recorded off it. Widens to further rails (e.g. `rtp`) without a breaking change.
      - `amount` integer: the leg amount in cents
      - `status` 'initiated' | 'submitted' | 'settled' | 'returned' | 'failed': the state of this leg
      - `error_code` string, nullable: normalised, rail-agnostic reason the leg failed, in snake_case (e.g. `insufficient_funds`). Stable across rails — **branch on this, not on `network_error_code`**, which is the network's own code and changes meaning between rails. Set when a leg is `failed` or `returned`, and null otherwise. A correction is not a failure: a leg that settled after a notification of change carries `network_error_code` but no `error_code`. A return code with no normalised equivalent reports `unclassified_return`, so this is never null on a leg that failed — the raw code is always there to fall back on.
      - `error_description` string, nullable: human-readable text for `error_code`, in English — for support and logs, not for branching. Null whenever `error_code` is.
      - `network_error_code` string, nullable: the raw code from the network, verbatim — an ACH return code (`R01`). Preserved for reconciliation. Present whenever the network said anything about this leg, which is not only when it went wrong: a notification of change carries an advisory code (`C01`) on a leg that **settled** normally. Read it together with `status` — the code alone does not mean the money did not move.
    - `metadata` object: any useful information you'd like to store alongside this payment
    - `created_at` string, date-time
    - `updated_at` string, date-time
  - `page_info` unknown, required

## Other responses

- `201`: Payee payment was scheduled successfully
- `400`: The request was malformed, failed validation, or referenced a resource that cannot be used in its current state — for example scheduling a payment to a payee that is not active, or creating anything under a payer account that is not active. When the failure is field-level, `error.details` carries one array of messages per rejected attribute.
- `401`: The access token is missing, expired, or invalid.
- `403`: The credentials are valid but not permitted to access this resource.
- `404`: No resource exists for the given id (within the scoped payer account).
- `409`: An `Idempotency-Key` was reused with a different request body. Retry with a fresh key, or resend the original body to get the payment that key already created.
- `422`: The request was well-formed and passed field validation, but a business rule rejected it — for example a payer funding account that is not `verified`, or a payee with no receiving bank account to credit. Field-level validation failures return `400`, not this. `error.details` carries per-attribute messages where the rule is attributable to one.
- `429`: The rate limit has been exceeded. Back off and retry after the interval indicated by the `Retry-After` header.

---

[API](https://skmtc.dev/justifi/apis/justifi-api-documentation.md) - [All operations](https://skmtc.dev/justifi/apis/justifi-api-documentation/llms.txt) - [OpenAPI document](https://skmtc.dev/justifi/apis/justifi-api-documentation/revisions/dd9f3fed7e46?raw)
