REST API

Operations

Poll the status of asynchronous event create, update, delete, and refund operations and their phases.

When you create, update, delete, or refund an event, the API responds immediately and performs the follow-up work asynchronously: recurrence fan-out, ticket structure setup, and (for Shopify-installed companies) Shopify product mirroring. This endpoint reports the status of that work as an operation.

Create, update, and refund return the operation id on the X-Event-Operation-ID response header. Delete (DELETE /events/{id}) returns the operation as its response body. If your integration needs to wait for the work to finish before proceeding (for example, a bulk import that creates an event and then immediately imports tickets), poll this endpoint until the operation reaches a terminal status (completed or failed).

The operation model

A save or delete operation is an aggregate. It does no work itself; it owns a tree of phases that perform the work, and its own status is a roll-up of theirs. A refund operation is a single leaf with no phases.

The phases present depend on the merchant type and the event, so do not assume a fixed set. See Phases.

Breaking change Aggregates (save, delete) pass through a running status between pending and the terminal completed / failed. If your integration treats “any status other than pending” as done, it will stop too early, while work is still in flight. Treat only completed and failed as terminal. (refund never reports running.)

Get operation

GET /events/{event_id}/operations/{id}

curl -X GET \
  https://app.guestmanager.com/api/public/v2/events/71390/operations/4291 \
  -H 'Authorization: Token abcdefg' \
  -H 'Content-Type: application/json'

Sample response — Shopify save in progress

{
  "id": 4291,
  "action": "save",
  "event_id": 71390,
  "status": "running",
  "progress": { "percent": 33, "completed": 2, "total": 6 },
  "error": null,
  "created_at": "2026-05-18T11:00:00Z",
  "updated_at": "2026-05-18T11:00:05Z",
  "phases": [
    { "id": 4292, "name": "tickets",    "status": "completed", "progress": { "percent": 100, "completed": 8, "total": 8 }, "error": null },
    { "id": 4293, "name": "catalog",    "status": "running",   "progress": null, "error": null },
    { "id": 4294, "name": "inventory",  "status": "completed", "progress": { "percent": 100, "completed": 3, "total": 3 }, "error": null },
    { "id": 4295, "name": "metafields", "status": "pending",   "progress": null, "error": null },
    { "id": 4296, "name": "delivery",   "status": "pending",   "progress": null, "error": null },
    { "id": 4297, "name": "publish",    "status": "pending",   "progress": null, "error": null }
  ]
}

Here the local ticket structure is already built (tickets is completed), so the registration types and products are usable, while the Shopify-side phases finish. See Detecting partial readiness.

Sample response — save failed (fatal phase)

{
  "id": 4291,
  "action": "save",
  "event_id": 71390,
  "status": "failed",
  "progress": { "percent": 50, "completed": 3, "total": 6 },
  "error": { "code": "INVENTORY_RECONCILE_RAISED", "message": "Shopify rejected the inventory adjustment.", "phase": "inventory" },
  "created_at": "2026-05-18T11:00:00Z",
  "updated_at": "2026-05-18T11:00:08Z",
  "phases": [
    { "id": 4292, "name": "tickets",   "status": "completed", "progress": { "percent": 100, "completed": 8, "total": 8 }, "error": null },
    { "id": 4294, "name": "inventory", "status": "failed",    "progress": null, "error": { "code": "INVENTORY_RECONCILE_RAISED", "message": "Shopify rejected the inventory adjustment." } },
    { "id": 4297, "name": "publish",   "status": "failed",    "progress": null, "error": { "code": "UPSTREAM_FAILED", "message": null } }
  ]
}

The aggregate’s error.phase names the originating phase (matching one of the phases[].name). Phases that could not run because a prerequisite failed are failed with error.code: "UPSTREAM_FAILED".

Sample response — save completed with a non-fatal phase failure

{
  "id": 4291,
  "action": "save",
  "event_id": 71390,
  "status": "completed",
  "progress": { "percent": 100, "completed": 6, "total": 6 },
  "error": null,
  "created_at": "2026-05-18T11:00:00Z",
  "updated_at": "2026-05-18T11:00:11Z",
  "phases": [
    { "id": 4292, "name": "tickets",    "status": "completed", "progress": { "percent": 100, "completed": 8, "total": 8 }, "error": null },
    { "id": 4297, "name": "publish",    "status": "completed", "progress": null, "error": null },
    { "id": 4295, "name": "metafields", "status": "failed",    "progress": null, "error": { "code": "METAFIELDS_RAISED", "message": "Metafield write timed out." } }
  ]
}

A completed aggregate does not guarantee every phase completed. The trailing phases metafields and delivery are best-effort: a failure stays visible in the tree (and is reported to our error tracking) but does not fail the operation, because the products are already built, stocked, and published by the time those run. If you need to assert that every step succeeded, check the individual phases statuses, not just the aggregate.

Sample response — non-Shopify save

{
  "id": 4310,
  "action": "save",
  "event_id": 71401,
  "status": "completed",
  "progress": { "percent": 100, "completed": 1, "total": 1 },
  "error": null,
  "created_at": "2026-05-18T12:00:00Z",
  "updated_at": "2026-05-18T12:00:02Z",
  "phases": [
    { "id": 4311, "name": "tickets", "status": "completed", "progress": { "percent": 100, "completed": 4, "total": 4 }, "error": null }
  ]
}

Sample response — delete

{
  "id": 4330,
  "action": "delete",
  "event_id": 71390,
  "status": "running",
  "progress": { "percent": 0, "completed": 0, "total": 2 },
  "error": null,
  "created_at": "2026-05-18T13:00:00Z",
  "updated_at": "2026-05-18T13:00:00Z",
  "phases": [
    { "id": 4331, "name": "tickets", "status": "running", "progress": null, "error": null },
    { "id": 4332, "name": "catalog", "status": "running", "progress": null, "error": null }
  ]
}

Response fields

Top node (the operation):

Field Type Description
id integer Operation ID. Matches the X-Event-Operation-ID header from the originating request.
action string save, delete, or refund.
event_id integer The event this operation acts on. Stable across its lifetime.
status string pending, running, completed, or failed. Poll until completed or failed. See Status.
progress object / null { percent, completed, total } or null. See Progress.
error object / null null until failed; then { code, message, phase }. See Errors.
phases array The phase tree (empty [] for refund). See Phases.
created_at string ISO 8601 timestamp of creation.
updated_at string ISO 8601 timestamp of the last status / progress change.

Phase node (an entry in phases):

Field Type Description
id integer Phase ID.
name string The phase name. See the Phases enum.
status string pending, running, completed, or failed.
progress object / null { percent, completed, total } or null when the phase has no determinate metric.
error object / null null, or { code, message } when this phase failed.

Phase nodes are intentionally slim: no event_id (identical to the operation), no timestamps, and no nested phases (the tree is always two levels).

Status

Status Terminal? Meaning
pending no Planned but not started (waiting on a prerequisite phase, or queued).
running no In flight. Aggregates pass through it; refund never reports it.
completed yes Finished successfully. On an aggregate, every fatal phase completed (a non-fatal phase may still have failed).
failed yes Finished unsuccessfully. error carries the root cause. On an aggregate this means a fatal phase failed.

Progress

progress is { percent, completed, total } or null.

  • On an aggregate (save / delete) it is a phase-count roll-up: completed and total count terminal phases against total phases, so the bar reaches 100% once every phase is terminal (even if a non-fatal phase failed). It is null before any phase is planned.
  • On a phase it is the work count when the phase has a determinate batch (tickets, inventory, metafields, delivery), and null otherwise (catalog, schedule, and any phase that has not started).

Treat progress as nullable everywhere, and use status (not progress) to decide whether a node is done.

Phases

phases is a flat list of the work the aggregate planned. Ordering is not significant and dependencies between phases are not exposed; track each phase by its status.

Stable The phase name values and the operation action values are a stable enum. New names may be added as the pipeline grows, but existing names will not change meaning. (The error.code values, by contrast, are an open set; see Errors.)

A phase’s name is the subsystem it touches; whether the phase builds or tears down that subsystem is determined by the operation’s action (save builds/syncs, delete tears down). There is no per-phase action field. So tickets builds the product/ticket structure under a save and removes it under a delete; catalog mirrors the metaobject graph under a save and deletes it under a delete.

name Subsystem Fatal to the operation?
schedule The child date occurrences of a recurring event. yes
tickets The local ticket / registration structure (and Shopify products, when connected). Carries determinate progress. Under delete, the product/event teardown. yes
catalog The event’s catalog metaobject graph in Shopify (bulk operations under save; removed under delete). save: yes · delete: no (best-effort)
inventory Shopify inventory levels, reconciled against ticket availability. yes
metafields Product / variant metafields in Shopify. no (best-effort)
delivery Digital delivery configuration for the Shopify products. no (best-effort)
publish Storefront publication of the products (after catalog and inventory). yes

Which phases appear

The planned set depends on the merchant type and the event. Phases that do not apply are never planned and never appear.

Merchant / event save phases
Non-Shopify, single-date tickets
Non-Shopify, recurring schedule, tickets
Shopify (custom app), single-date tickets, inventory, metafields, delivery, publish
Shopify (standard app), single-date tickets, catalog, inventory, metafields, delivery, publish
Shopify (standard app), recurring adds schedule to the front of the standard set

delete plans tickets (the event/product teardown) plus catalog for Shopify standard-app events (the metaobject-graph teardown). refund plans no phases (phases is []).

Detecting partial readiness

Because save is an aggregate, the merchant’s “I can work with this now” moment arrives before the operation finishes. When the tickets phase reaches completed while the operation is still running, the local ticket structure and (for Shopify) the products exist and are editable; the remaining phases are Shopify-side finishing work. If your integration only needs the tickets/products to exist, you can proceed at that point.

Per-operation summary

action Status path phases
save pendingrunningcompleted / failed the phase tree (varies)
delete runningcompleted / failed (returned as running) tickets (+ catalog on Shopify)
refund pendingcompleted / failed [] (leaf, no phases)

Errors

error is null until a node fails, then { code, message } (plus phase on an aggregate). code is a stable, machine-readable string; message is human-readable copy that may change without notice. Always switch on code, not message. On an aggregate, error carries the root-cause phase’s code and names it in phase.

Preview The error.code set is open and additive. New, more specific codes will be introduced as failure modes become worth distinguishing; existing codes will not change meaning. Match coarsely and treat any unrecognized code as a generic failure.

Code Meaning
FAILED Generic failure: the underlying batch chain exhausted retries. See message for detail.
UPSTREAM_FAILED A prerequisite phase failed, so this phase never ran. Look at the other phases for the real cause.
*_RAISED A specific phase raised while running (e.g. INVENTORY_RECONCILE_RAISED, METAFIELDS_RAISED, SESSION_BUILD_RAISED, DELIVERY_RAISED).
catalog-specific Shopify catalog phase failures, e.g. BULK_OP_FAILED, METAFIELDS_ROW_ERRORS, RESULT_FILE_ROW_ERRORS.

Polling guidance

  • Start polling with a short delay. Simple date events typically finish in 1 to 5 seconds; recurring events with many occurrences and Shopify catalog mirroring take longer.
  • Use exponential backoff: 1s, 2s, 4s, 8s, capped at 15s.
  • Stop polling only when status is completed or failed. pending and running are both non-terminal.
  • A 404 Not Found means the operation id does not exist, does not belong to the event in the URL, or is a phase id rather than an operation id. Poll with the id from X-Event-Operation-ID, not a phases[].id.
  • The underlying records are retained for the lifetime of the operation, so you can fetch the final status long after completion.

Deprecated aliases

For a transition window, the pre-rename path and header continue to work and are equivalent to the canonical ones above. Migrate to the canonical names; the aliases will be removed on or after September 4, 2026, as announced in the changelog.

Deprecated Canonical
GET /events/{id}/sync_jobs/{id} GET /events/{id}/operations/{id}
X-Event-Sync-Job-ID header X-Event-Operation-ID header

What happens for non-Shopify customers

The pipeline runs the same regardless of merchant type. Every company gets the tickets phase (and schedule for recurring events). The Shopify phases (catalog, inventory, metafields, delivery, publish) are planned only for Shopify-installed companies and are simply absent otherwise. The operation completes normally either way.