---
title: "Add, update, or cancel up to 1,000 imported tickets"
method: POST
path: "/api/v1/imports/tickets"
tags: ["Jobs"]
---

# Add, update, or cancel up to 1,000 imported tickets

`POST /api/v1/imports/tickets`

## Request

- Base URL: `https://api.agendaforge.app`
- URL: `https://api.agendaforge.app/api/v1/imports/tickets`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Sends ticket rows from your own ticketing system to one event, with the same rules as a file import in the app. Requires the tickets:import scope (public_api alone is not enough). Each row is matched by reference: a new reference creates a ticket and needs a name (name, or firstName and lastName) and a ticketType; a reference that already exists may carry only what changed. Rows not in the request are left alone. The event must be activated and set up for imported tickets, and only one import runs per event at a time. Up to 100 tickets are applied inside the request and answer 200 with the job already completed; 101 to 1,000 tickets answer 202 Accepted with a statusUrl (also sent as the Location header) to poll at GET /api/v1/jobs/{id}. Rows fail independently: a row problem counts toward result.failed and is listed in result.errors (first 50) without stopping the other rows. Whole-request problems (an unreadable body, a bad field type, duplicate or too-short scanKey values, more than 50 ticket types) answer 400 and nothing is imported. Besides the per-key limit, each event accepts 12 ticket import requests per minute. With an Idempotency-Key, a repeat of the same request returns the original response (status, body, and Location) with Idempotent-Replay: true, not current progress; poll statusUrl for that.

## Headers

- `Idempotency-Key` string

## Request body

- ImportTicketsRequest
  - `eventId` string, required: The event to import into; must be owned by the key's organization, activated, and set up for imported tickets.
  - `tickets` TicketImportRow[], required
    - `reference` string, required: Your system's ticket or order line id. It is how a later request updates or cancels the same ticket, so keep it stable.
    - `name` string: Full name; split into first and last name. Ignored when firstName or lastName is sent. 120 characters at most.
    - `firstName` string
    - `lastName` string
    - `email` string, email
    - `company` string
    - `title` string: Job title.
    - `ticketType` string: Ticket type name. An unknown name creates the type. At most 50 different values per request.
    - `status` string: Case-insensitive. Valid: valid, active, paid, complete, completed, confirmed, attending, issued. Cancelled: cancelled, canceled, void, voided. Refunded: refunded, refund. Any other word fails the row with unknown_status.
    - `table` string
    - `seat` string
    - `scanKey` string: Optional. The value inside the attendee's existing QR code, so it scans at the door. Must be unique within the event. Stored only as a hash.
    - `purchasedAt` union: Purchase date as a date string (e.g. 2026-11-04) or a millisecond timestamp.
      - string
      - number
    - `answers` object[]: Registration answers to keep with the ticket.
      - `label` string, required: Question label; up to 120 characters are kept.
      - `value` string, required: Answer; up to 300 characters are kept.
  - `confirmCheckedInCancels` boolean: A ticket that has already been checked in is cancelled or refunded only when this is true. Otherwise that row fails with the checked_in code.

## Response `200`

Up to 100 tickets: the import finished inside the request. The job is completed and result carries the final counts.

- TicketsImportJob: Returned by POST /api/v1/imports/tickets and by GET /api/v1/jobs/{id} for a tickets_import job.
  - `jobId` string, required: Opaque job id.
  - `type` 'tickets_import', required
  - `status` 'pending' | 'processing' | 'completed' | 'failed', required: completed in a 200 response; pending in a 202 response. Terminal states are completed and failed; keep polling until one is reached.
  - `total` integer, required: Tickets in the request.
  - `statusUrl` string, uri, required: Poll this URL (GET, a key with tickets:import) for progress.
  - `result` object, required
    - `total` integer, required
    - `processed` integer, required: Rows handled so far, failed rows included; equals total when done.
    - `created` integer, required: New tickets.
    - `updated` integer, required: Existing tickets that changed.
    - `unchanged` integer, required: Existing tickets with nothing to change.
    - `cancelled` integer, required: Tickets cancelled or refunded.
    - `restored` integer, required: Cancelled or refunded tickets made valid again.
    - `failed` integer, required: Rows not applied.
    - `errors` object[], required: Row failures (first 50). code is one of missing_reference, reference_too_long, reference_mangled, reference_reserved, duplicate_reference, missing_name, invalid_email, missing_ticket_type, unknown_status, scan_key_reserved, scan_key_length, scan_key_taken, invalid_date, field_too_long, missing_fields, checked_in, event_full, type_catalogue_full. A job that stops early reports one CONFLICT entry and status failed.
      - `index` integer, required: Zero-based position in the tickets array.
      - `reference` string, nullable, required: The row's reference, or null when it is withheld.
      - `code` string, required
      - `message` string, required

## Other responses

- `202`: 101 to 1,000 tickets: the import was accepted and runs in the background. Poll statusUrl (also in the Location header) until status is completed or failed.
- `400`: The body is not a JSON object of at most 2 MB, eventId is missing, tickets is not an array of 1 to 1,000 objects, a field has the wrong type, or the rows break a whole-request rule (duplicate or too-short scanKey values, more than 50 ticket types, more than 12 answers or an over-long value in a row). Nothing was imported.
- `401`: The API key is missing, malformed, invalid, expired, or revoked.
- `402`: The event is not activated yet. Ticket imports need an activated event.
- `403`: The key lacks the tickets:import scope, or the event does not belong to the key's organization.
- `404`: No such endpoint under /api/v1. The message lists the valid endpoints.
- `409`: Another import is already running for this event, the event is not set up for imported tickets, or the Idempotency-Key was already used for a different endpoint or a different request body.
- `429`: The fixed window for this API key (120 requests per minute) or IP (30 per minute, unauthenticated) is exhausted. Wait Retry-After seconds and retry.
- `500`: An unexpected server failure. The body still follows the JSON error contract; retry with exponential backoff.
- `default`: Any other failure, including 404 NOT_FOUND for an unknown path and transient 5xx errors. Always the same JSON error contract.

---

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