Retrieve tickets for one of my fulfillment orders

Returns the tickets issued for a specific fulfillment order belonging
to the authenticated end-user, plus a short-lived presigned download
URL for the consolidated ticket PDF when one has been produced.

The order is resolved by fulfillment_order_id and must belong to
the JWT-authenticated user. If the order does not exist or it
belongs to a different user, 404 Not Found is returned without
distinguishing between the two — this prevents enumeration of order
IDs across users.

If the order exists and belongs to the user but tickets have not
yet been assigned (status PENDING / CONFIRMED), 409 Conflict
is returned with the current status so the caller can poll.

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

Part of ticketing — Tickets and fulfillment explains the order lifecycle and how tickets are issued.

Why this endpoint exists

The list endpoint gives you barcodes but no PDF. This one adds the deliverable: a short-lived presigned URL for the consolidated ticket PDF, generated on demand.

It is also the endpoint with a proper polling contract. While tickets are still being issued it returns 409 with a Retry-After header, which is a far better signal than the empty tickets array the list returns.

Which id goes here

This route takes the fulfillment_order_id, not the checkout order_id.

They are different values, and the platform exposes two sibling routes that each accept one of them:

RouteId to useWhere it comes from
GET /me/fulfillment/orders/{fulfillment_order_id}/ticketsfulfillment_order_idThe list response
GET /me/fulfillment/orders/{order_id}order_idConfirm checkout

Passing the wrong one gets you a 404 that looks exactly like a missing order. If you hold only the checkout order_id — which is the case right after purchase, and the only case for guests — use the sibling route.

Request

curl "https://api.acc.funtrips.io/v2/me/fulfillment/orders/$FULFILLMENT_ORDER_ID/tickets" \
  -H "x-campaign-id: $CAMPAIGN_ID" \
  -H "Authorization: Bearer $END_USER_TOKEN"

Response

200 OK — the order, its tickets, and the deliverable when one exists.

{
  "data": {
    "fulfillment_order_id": "7c1b3f9a-2d4e-4a6f-9b1a-3c5e7d2f8a0b",
    "order_id": "5e2a7c91-3b6d-4f82-a17c-9d4e6b1f3a25",
    "status": "COMPLETED",
    "total_price": { "value": "52.97", "currency": "EUR" },
    "tickets": [
      {
        "fulfillment_line_id": "af31c8d2-59e4-4b17-8c6a-2d0f7e3b9145",
        "product_title": "Dagticket volwassene",
        "merchant_title": "Amsterdam Zoo",
        "barcode": "3041234567890",
        "symbology": "EAN_13",
        "effective_price": { "value": "18.50", "currency": "EUR" },
        "validity": { "…": "validity window" }
      }
    ],
    "deliverable": {
      "file_name": "tickets-7c1b3f9a.pdf",
      "file_type": "application/pdf",
      "download_url": "https://s3.eu-west-1.amazonaws.com/…?X-Amz-Signature=…",
      "expires_at": "2026-09-03T15:20:00Z"
    }
  },
  "meta": { "correlation_id": "…" }
}

download_url expires — fetch it now

It is a presigned S3 URL with a short life, and expires_at is when it dies.

Stream the bytes immediately. Do not store the URL, e-mail it, or put it in a push notification — by the time anyone clicks, it is dead. If the customer needs the PDF again, call this endpoint again for a fresh URL.

deliverable is absent when no PDF has been produced. That is legitimate: not every order gets one — an order whose lines are all shipped physically has no ticket document. Fall back to rendering the barcodes.

Rendering barcodes

Each ticket carries barcode (the raw string as issued by the provider) and symbology (its encoding): QR, CODE128, CODE_39, EAN_13, PDF417, ITF, EAN_8, UPC_A, ITF_14, DATA_MATRIX.

Render barcode using the encoding named in symbology. Do not guess from the string's shape — a 13-digit value is not necessarily EAN_13, and a scanner at the gate will reject a mis-encoded barcode.

📘

Match on the exact strings: CODE128 carries no underscore while CODE_39 does.

The 409 polling contract

When the order exists and belongs to the customer but tickets are not assigned yet (PENDING / CONFIRMED), the response is 409 with a Retry-After header in seconds (typically 10).

HTTP/1.1 409 Conflict
Retry-After: 10
Content-Type: application/problem+json

Wait at least Retry-After, then retry with exponential backoff, capped around 5 minutes.

📘

Read the delay from the Retry-After header — it is set on every 409. If you also

need the order's current lifecycle state, take it from the list endpoint.

async function fetchTickets(fulfillmentOrderId, campaignId) {
  let delay = 10_000;

  for (let attempt = 0; attempt < 8; attempt++) {
    const res = await fetch(
      `${BASE}/me/fulfillment/orders/${fulfillmentOrderId}/tickets`,
      { headers: { 'x-campaign-id': campaignId }, credentials: 'include' },
    );

    if (res.ok)              return (await res.json()).data;
    if (res.status !== 409)  throw new Error(`unexpected ${res.status}`);

    const retryAfter = Number(res.headers.get('Retry-After')) || 10;
    await new Promise(r => setTimeout(r, Math.max(retryAfter * 1000, delay)));
    delay = Math.min(delay * 1.5, 300_000);
  }
  return null;   // still not ready — the e-mail will arrive regardless
}

Give up gracefully. The ticket e-mail is sent independently of whether your client is still polling.

Errors

StatusMeaningWhat to do
401Token missing, expired or invalidRe-authenticate
404No such order — or it belongs to another customerSee below
409Tickets not ready yetHonour Retry-After and retry
500Unexpected faultRetryable

Fulfillment errors carry no type — branch on status.

404 deliberately hides ownership

An order that exists but belongs to a different customer returns 404, identical to one that does not exist. The two are indistinguishable so that order ids cannot be enumerated across accounts.

Practically: a 404 does not prove the id is wrong. Check you are using the fulfillment_order_id and not the order_id, and that the token belongs to the customer who placed the order.

Path Params
uuid
required

Platform-generated identifier of the fulfillment order.

Headers
uuid
required

Campaign scope for this request. Filters content to the specified campaign.

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

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