---
title: "The payer gets the job's unspent money back to the card (ALIP-0068, Manage-key refunds)"
method: POST
path: "/manage/{job_id}/refund"
tags: ["jobs"]
---

# The payer gets the job's unspent money back to the card (ALIP-0068, Manage-key refunds)

`POST /manage/{job_id}/refund`

## Request

- Base URL: `https://pact0.com/api/v1`
- URL: `https://pact0.com/api/v1/manage/{job_id}/refund`
- Auth: none required

The ALIP-0068 refund of POST /escrow/envelopes/{envelope_id}/refund,
through the manage key: the budget is derived from the job and
never taken from the request (the body may carry only
`amount_micro`; any other field, a budget id included, is refused
with `422 invalid_body`), the refund runs as the job's guest actor
(the budget's funder), and the money goes back only to the card
that paid. Error codes are ALIP-0068's, except that
`missing_session` and `envelope_not_found` do not occur here; a
missing or wrong key is `401 manage_key_invalid`.

## Headers

- `X-Pact0-Manage-Key` string, required
- `Idempotency-Key` string

## Request body

- object
  - `amount_micro` integer: Money in **micro-units**. 1 USD = 1,000,000 micro. **WARNING — do not confuse with cents.** Every `*_minor` field in this spec uses this convention, NOT the Stripe-style minor-currency-unit (cents) convention. Misreading the suffix as cents and multiplying by 100 yields a 10,000× misposting. Conversion examples: - `$0.05` (the test-pool fixture price) is `5_000_000 ÷ 100 = 50_000` - `$1.00` (the minimum buyer envelope) is `1_000_000` - `$5.00` (the dispute stake floor) is `5_000_000` - `$50.00` (the dispute stake ceiling) is `50_000_000` - `$5,000.00` (the maximum buyer envelope) is `5_000_000_000` Conversion to Stripe's cents (÷10,000, round down) happens only at the API boundary in the platform's wallet-backend implementations. Clients never invert this. **The `_usd` twin.** On job, claim and balance responses every `<base>_minor` / `<base>_micro` field has a `<base>_usd` sibling: the same amount in US dollars, as a number (`amount_minor: 50000` → `amount_usd: 0.05`). Read the twin; do arithmetic on the integer. Trio v8 §4.2 (2026-05-26): added because a REST-only buyer reading bare `{ type: integer, format: int64 }` schemas could plausibly assume cents. The bounds on POST /escrow/envelopes (`minimum: 1000000, maximum: 5000000000`) cap the worst-case misposting but do not close the confusion vector; surfacing the convention at every field site does.

## Response `200`

Settled (`refunded` or `partially_refunded`), as on the session route.

- EnvelopeRefundResult
  - `success` true
  - `data` object
    - `envelope_id` string
    - `status` 'refunded' | 'partially_refunded' | 'pending'
    - `refunded_micro` string: Accepted by Stripe.
    - `refunded_usd` number: Same amount in US dollars (the `_usd` twin of the micro-unit field beside it; see MoneyMicro).
    - `pending_micro` string: Outcome unknown; reserved; settled by the sweep. Not spendable meanwhile.
    - `pending_usd` number: Same amount in US dollars (the `_usd` twin of the micro-unit field beside it; see MoneyMicro).
    - `failed_micro` string: Definitely refused by Stripe and already back in the budget.
    - `failed_usd` number: Same amount in US dollars (the `_usd` twin of the micro-unit field beside it; see MoneyMicro).
    - `currency` string
    - `refund_ids` string[]
    - `remaining_micro` string: Spendable in the budget afterwards (excludes pending_micro).
    - `remaining_usd` number: Same amount in US dollars (the `_usd` twin of the micro-unit field beside it; see MoneyMicro).

## Other responses

- `202`: Sent, outcome not yet known (`pending`). Do not send it again; the same Idempotency-Key is safe.
- `400`: `invalid_json` — a body was sent but is not JSON; `unknown_query_param`.
- `401`: `manage_key_invalid` — the manage key is missing, malformed, unknown, or belongs to another job (one answer for all four). `manage_key_revoked` — the job was attached to a pact0 account, which switched the link off; manage it signed in. Every refused key counts against the caller's IP; past the limit the answer is `429 rate_limited`.
- `409`: `envelope_not_refundable`, `nothing_to_refund`, `no_refundable_charge`, `refund_limited_by_open_jobs`, `refund_not_available_legacy_budget`, `refund_settlement_pending` (the card payment is still being confirmed; retry later).
- `422`: `invalid_body` (only `amount_micro` is accepted), `amount_exceeds_unspent`, `amount_below_one_cent`, `balance_envelope_not_refundable`, `idempotency_key_conflict`.
- `429`: Too many requests
- `502`: `refund_failed_at_stripe` — nothing was sent and nothing taken; a retry is safe.
- `503`: `refund_not_enabled` — returning money to a card is switched off; nothing changed.

## Changes

- **2026-10-20** `e48bb10d300f`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/pact0/apis/pact0-api/changes/manage/:job_id/refund/post.md)

---

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