Starts a new fulfillment workflow for an existing completed order,
replacing the visit date with new_date. Supports idempotency via
the Idempotency-Key header.
The original order must be in COMPLETED status and must belong to
the authenticated user and their campaign. The old tickets are voided
with the provider on a best-effort basis — if voiding fails the new
order still proceeds and the old tickets will expire naturally.
The response is synchronous and returns only the new fulfillment order
ID and the requested date. The actual ticket issuance is asynchronous.
Poll GET /fulfillment/orders/{order_id} (using the checkout order_id
of the new order) to retrieve tickets once the workflow completes.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
Part of ticketing — Tickets and fulfillment explains the order lifecycle and how tickets are issued.
Why this endpoint exists
Plans change. A customer holding tickets for next Tuesday wants to come on Saturday instead, and the alternative to supporting that is a refund plus a repurchase — which loses the sale and costs you a support conversation.
A date change is not an edit. It issues a new fulfillment order for the new date, voids the old tickets, and charges the difference if the new date costs more. The customer ends up with fresh barcodes.
What it costs
The amount due is the sum of two independent parts:
The price difference. Per-ticket price increase on the new date. Increases only — a cheaper date is not refunded. Moving from a €24 Tuesday to a €29 Saturday costs €5 per ticket; moving the other way costs nothing and returns nothing.
The date-change fee. The campaign's fixed per-order fee, from date_change_cost in campaign config. Campaigns without one fall back to a per-ticket fee.
If the customer bought the flexible-date option (flexible_date: true on the order), the fee is waived — but the price difference is not. Owning flexibility buys you a free change, not a free upgrade. Say that plainly in your UI, or customers who paid for flexibility will be surprised by a charge.
When the total is zero, no payment is needed and the new tickets are issued straight away.
Preconditions
| Requirement | Consequence if unmet |
|---|---|
Original order is COMPLETED | 422 |
| Order belongs to the authenticated customer and campaign | 404 |
| Visit date is tomorrow or later | 422 — see below |
| No unpaid date change already pending | 409 |
Same-day changes are refused.An order whose visit date is today or already past cannot be moved:
422, "the visit date is too close to be changed". Only future visits are movable. Hide the change-date affordance once the visit date arrives, rather than surfacing a rejection.
Request
curl -X POST https://api.acc.funtrips.io/v2/fulfillment/reorder \
-H "Authorization: Bearer $END_USER_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"order_id": "5e2a7c91-3b6d-4f82-a17c-9d4e6b1f3a25",
"new_date": "2026-09-19T10:00:00Z",
"return_url": "https://tickets.your-brand.example/order/date-changed"
}'| Field | Type | Required | Notes |
|---|---|---|---|
order_id | UUID | Yes | The checkout order_id of the completed order |
new_date | date-time | Yes | New visit date and time, UTC. The time component is preserved for timeslotted products |
return_url | uri | Yes | Where the customer returns after paying. Required even when the change turns out to be free |
return_url is mandatory regardless of cost, because whether a payment is needed is only known after pricing. Supply a real URL every time.
For a timeslotted product, fetch timeslots for the new date first and send the chosen entry_from — the same rule as at checkout, including the prohibition on midnight fallbacks.
Response
202 Accepted — the workflow has started.
{
"data": {
"fulfillment_order_id": "b7e2c4a9-1f38-4d65-9a02-7c5e1b3f8d40",
"new_date": "2026-09-19T10:00:00Z",
"amount_due": { "value": "12.50", "currency": "EUR" },
"payment_url": "https://checkout.psp.example/pay/xyz789"
},
"meta": { "correlation_id": "…" }
}Free change — both nullable fields come back null:
{
"data": {
"fulfillment_order_id": "b7e2c4a9-1f38-4d65-9a02-7c5e1b3f8d40",
"new_date": "2026-09-19T10:00:00Z",
"amount_due": null,
"payment_url": null
}
}Branch on payment_url: non-null means redirect the customer to pay; null means the change is done and tickets are being issued. Handle both — whether a change is free depends on campaign configuration and the two dates, not on anything your client controls.
Tickets are issued after payment
202 means the workflow started, not that the change is complete. When there is an amount due, new tickets are issued only after successful payment, within the payment window.
Poll GET /me/fulfillment/orders/{order_id} with the original order_id — it resolves to the current fulfillment order for that id, so once the change completes it returns the new tickets. The original order appears in the list as REORDERED.
If the customer abandons the payment, nothing changes: the original tickets stay valid, and the pending change can be cancelled or simply superseded.
Old tickets are voided best-effort
The original tickets are voided with the provider on a best-effort basis. If voiding fails, the new order still proceeds and the old tickets expire naturally.
So there is a window in which both sets exist. This is the deliberate trade-off — a provider outage should not block a customer's date change — but it means your UI should show only the current order's tickets and filter REORDERED orders out of the wallet view.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Missing field, malformed date, or no Idempotency-Key | Fix the request |
404 | Order not found, or not the caller's | Check the order_id and the session |
422 | Order not COMPLETED; visit date too close; not a movable order | Surface the reason; hide the affordance |
409 | A date change for this order is already awaiting payment | Cancel it or let the new one supersede it |
502 | Could not price the new date, or could not create the difference payment | Retryable with the same key |
500 | Workflow could not be started | Retryable with the same key |
No type field on fulfillment errors — branch on status.
Notes
- Reuse one
Idempotency-Keyper change intent. A double-clicked "confirm new date" must not create two changes and two payments. - Starting a new change supersedes an unpaid one automatically, so calling cancel first is optional.
- Once the difference is captured, the change cannot be cancelled — returning that money is a refund, not a cancel.
