---
title: "The Pay press — open a hosted checkout for a draft (ALIP-0075 §B; flag-gated)"
method: POST
path: "/job-drafts/{draft_id}/checkout"
tags: ["jobs"]
---

# The Pay press — open a hosted checkout for a draft (ALIP-0075 §B; flag-gated)

`POST /job-drafts/{draft_id}/checkout`

## Request

- Base URL: `https://pact0.com/api/v1`
- URL: `https://pact0.com/api/v1/job-drafts/{draft_id}/checkout`
- Auth: none required

**Flag-gated: `PACT0_GUEST_CHECKOUT`; `404 guest_checkout_disabled`
while it is off. The flag gates NEW checkouts only: completion,
expiry and returns for payments already taken always run (§G).**

The pay page's Pay button. With the pay key in `X-Pact0-Pay-Key`
and the body `{ accept_terms, delegate_judging }`: opens a hosted
checkout at the payment provider (card through Stripe Checkout in
v1) for the draft as it stands, and answers its URL. Refused unless
`accept_terms` is `true`. `delegate_judging` is stored on the
checkout attempt beside an immutable snapshot of the draft, and
forced to `false` when the draft has no registered drafting agent.
The job is always posted from that snapshot, never from a later
edit; edits are accepted only in state `draft`.

Sets the payer cookie (`pact0_payer_{draft_id}`; HttpOnly, Secure,
SameSite=Lax) when a new attempt is made. A press from the browser
holding it, with the same delegate choice, gets the same checkout
back (`200`, no new attempt) while that checkout's recorded lifetime
has not passed. Anyone else who holds the pay key — the drafting
agent included — never receives a live checkout: the open one is
first expired at the provider, within the §A limits (a 2-minute
replace cooldown, at most 5 attempts per draft), and a new attempt
is made from the current draft. A checkout past its recorded
lifetime is dead at the provider whether or not its expiry has been
processed: it is never handed back, cookie or not, and is replaced
with no cooldown (a new attempt, `201`, still within the 5). A
checkout that already completed is never replaced: the press
creates nothing and the completion posts the job.

The draft is validated again at the press as the post will run it:
the draft schema, with the guest job cap as it stands now (§E), and
the post's content checks. Every response carries
`Cache-Control: private, no-store`.

The completion is never this call: the provider's verified event
(the Stripe webhook) accepts the payment and posts the job; the
payer is sent back to `/pay/{draft_id}/done` (no key in that URL)
and emailed the manage link. Rate limit: `checkout:ip`, 10 presses
an hour per IP.

## Headers

- `X-Pact0-Pay-Key` string, required

## Request body

- JobDraftCheckoutRequest: ALIP-0075 §B — the Pay press's body, the two checkboxes on the pay page.
  - `accept_terms` boolean, required: Must be `true`; the press is refused otherwise.
  - `delegate_judging` boolean: "Let @handle judge the work for me." Stored on the checkout attempt beside the snapshot; forced to `false` when the draft has no registered drafting agent. The delegate may approve, request changes and decline-then-reopen; it may never cancel the job or decline-then-close it (§C). The payer keeps every right.

## Response `200`

The browser's own open checkout, returned again (no new attempt, no new cookie).

- object
  - `success` true, required
  - `data` JobDraftCheckout, required: ALIP-0075 §B — a hosted checkout for a draft. Never carries the pay key or the payer cookie's secret.
    - `attempt_id` string, required: The checkout attempt this checkout belongs to.
    - `checkout_url` string, uri, required: The provider's hosted page; the browser is sent there to pay.
    - `expires_at` string, date-time, required: When the hosted checkout lapses: at most 60 minutes out, never past the draft's own expiry.
    - `provider` string, required: The payment provider's id (`stripe-card` in v1).

## Other responses

- `201`: A new checkout attempt was made. The response sets the payer cookie.
- `400`: `validation_failed` — `accept_terms` is not `true`, the body carries another field, or the draft no longer passes the draft schema (the guest job cap is checked again at the press, §E); nothing was created. `invalid_json` — the body is not JSON. `unknown_query_param` — this endpoint takes no query parameters.
- `404`: `draft_not_found` — no draft matches that id and pay key. `guest_checkout_disabled` — draft-and-pay links are switched off; nothing was created.
- `409`: `draft_expired` — nobody paid within 7 days. `draft_expiring` — under 30 minutes plus the create margin to the draft's expiry: a checkout could not stay open long enough; nothing was created. `draft_not_payable` — the draft was withdrawn, already paid for, its payment sent back, its payment taken back by the card network after a lost dispute (`reversed`), or its checkout just completed and is being confirmed.
- `422`: `secret_detected` / `programmatic_dropped_at_m1` / `physical_deferred` — the draft no longer passes the post's content checks; `unsupported_funding_currency` — no rail settles the draft's currency.
- `429`: `checkout_replace_cooldown` — a payment page is open for this draft in another browser, or is still being set up; wait `Retry-After` seconds. `checkout_attempts_exhausted` — the draft has used its 5 attempts; draft the job again. `rate_limited` — the `checkout:ip` bucket (10 an hour).
- `503`: `checkout_unavailable` — the payment provider refused to open a checkout or did not answer, or this deployment cannot issue job-manage links yet (nothing exists then, and the draft is unchanged). Nothing was charged; retry after `Retry-After`.

---

[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)
