Create and finalize a contractor payroll run

Create a Contractor payroll run and finalize it in one call. The body is the same as a Contractor create, except that externalId is required. The run is funded and its payables start paying out, as with POST /payments/payroll-runs/{payrollRunId}/finalize. Returns 201 with the new run.

Replaces V1/V2 POST /payments/pay-approved. To pay every approved payable, send {"type": "Contractor", "currency": "USD", "selection": {"scope": "ApprovedPayables"}, "externalId": "<your id>"}. A run pays payables in one currency, so make one call per currency.

externalId identifies the request. If the account already has a payroll run with this externalId, the call creates, funds and changes nothing, and answers by that run:

  • Same type, currency and selection, and the run is Paid, Processing or Failed:
    200 with the run.

  • Same request, and the run is a Draft: 409 ResourceConflict with detailCode
    payments.PayrollRunExternalIdDraftExists; its detail names the run. The Draft still
    holds its payables, so nobody is paid until you finalize it
    (POST /payments/payroll-runs/{payrollRunId}/finalize) or delete it, then retry.

  • Same request, and the run is Cancelled: 409 ResourceConflict with detailCode
    payments.PayrollRunExternalIdCancelled. Use a new externalId.

  • A different type, currency or selection: 409 ResourceConflict with detailCode
    payments.PayrollRunExternalIdConflict.

This is not an upsert: the existing run is never updated. A new externalId starts a new payroll; use one to pay again after a Failed run.

Retrying: after a 5xx, a timeout or no response, send the same body with the same externalId. The funding may already have happened, and the same externalId cannot pay twice. A retry with the same Idempotency-Key within 10 minutes returns 409 IdempotencyKeyConflict with retryAfter (seconds to wait). A retry with a new Idempotency-Key returns 409 ResourceConflict while the first call is still running, and then answers by the run as described above (200 once it was funded or failed).

Outcomes when the call does not return a funded run. In each case no Draft run is left holding payables:

  • No payroll funding source, or an account that cannot fund payroll yet: 422, normally
    before any run is created.

  • No eligible payable matches the selection: 422 with detailCode
    payments.PayrollRunNoEligiblePayablesMatched; no run is created.

  • A selected payable stopped being payable after the run was created (paid, changed,
    unapproved, or claimed by another run): the run is funded without it. If none is left,
    the call returns 422 with detailCode payments.PayrollRunNoEligiblePayablesAtFinalize
    and the run is deleted.

  • Funding failed: the run is left Failed, its payables are released, and the 422
    carries the run's failure reason as its detailCode. List that run with
    filter[failureCode][anyOf] or filter[externalId][eq].

  • Another create-and-finalize call is running for the account: 409 ResourceConflict.

  • A payable changed at the moment funding started (a race): 409 ResourceConflict, and
    the run is deleted, so a retry with the same externalId tries again.

  • Funding started but its outcome could not be confirmed: the run is left Processing and
    the call returns 409 ResourceConflict with detailCode
    payments.PayrollRunOutcomeUnknown; its detail names the run. Do not run the payroll
    again for these payables: get the run, and contact support if it stays Processing.

Requires a session with recent MFA step-up for HighRiskWriteAction. If step-up is missing or expired, this operation returns 403 StepUpMfaRequired; create and verify an MFA challenge at /v3/platform/mfa-challenges with requiredFor: "HighRiskWriteAction", then retry.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Body Params

The Contractor create body with externalId required. The externalId identifies the request: a retry with the same externalId returns the run the first call made.

string
enum
required
Allowed:
string
enum
required

The run's currency; every payable on the run must be in it. An unsupported currency returns 422.

selection
object
required

Which payables a Contractor run pays. With SpecificPayables, the request fails if any listed payable is not approved, not in the run's currency, or already in a run.

string

Your own stable key for the run. It is not used to detect duplicate creates; send an Idempotency-Key for that.

string
required
length ≥ 1
\S

Your own id for this payroll. Required. A retry with the same externalId returns the existing run instead of paying again.

metadata
object

Free-form key-value pairs. Max 50 keys; key length at most 40 characters; value length at most 500 characters. Where a list endpoint declares metadata filtering, it uses the QueryQL namespace via filter[metadata.{key}][eq]=value or filter[metadata.{key}][in][]=value. Endpoints that do not declare the dynamic Metadata filter do not support Metadata filtering.

Headers
string
required
length between 1 and 255
^[\x21-\x7e]{1,255}$

REQUIRED idempotency token for money-moving requests (e.g. transfers). The token is used by the API so a retry after a 5xx/timeout replays the original result instead of moving funds twice. Reuse the same key to retry safely; use a NEW key to issue a genuinely new transfer. Use 1-255 printable ASCII characters.

string
^(?:[A-Za-z0-9_.]{22}|[a-f0-9]{24})$

Select the Account for an Account-scoped operation. A direct ServiceAccount API key MUST supply this header, and the target must be within the ServiceAccount owner's or Authorization grant's Account boundary. A Person bearer may select an Account on which it has an active Stakeholder, and an Account session may select its bound Account (or a descendant only when the session explicitly includes descendants).

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
Credentials
Bearer
JWT
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json