Async job polling

The exact poll flow for background work on the Poptin API: read object_id and object_name from the 202 response, poll GET /v1/bulk-jobs, and handle terminal states.

Some write operations on the Poptin API do not finish inline. They return HTTP 202 Accepted along with a bulk_job object, run in the background, and require your client to poll until the job reaches a terminal state. This page documents that poll flow precisely - the endpoint to call, the fields to read, and how to interpret the result.

For the conceptual overview, see Async jobs. All operations are served under https://api.popt.in/v1 and authenticated with a pk_live_ bearer key from My Account → API & Connections → API Keys.

Operations that queue

The following endpoints respond with 202 Accepted and a bulk_job:

  • Bulk import contacts (JSON)
  • Bulk import contacts (CSV)
  • Bulk delete contacts (JSON)
  • Send template email

Single-item operations (create/update contact, delete contact, update status) complete inline and do not use this flow.

Poll flow

Every async job follows the same four steps:

  1. Call the queuing endpoint. On success, the server accepts the work and returns HTTP 202 with the standard response envelope. Reading the envelope's data.bulk_job gives you the identifiers you need.

  2. Read object_id and object_name from data.bulk_job. Together they uniquely identify this run:

    • data.bulk_job.object_id - the job's identifier.
    • data.bulk_job.object_name - the job type (for example json_bulk, csv_import, json_bulk_delete, template_email_send).
  3. Poll job status. Call Show bulk job status at:

    GET /v1/bulk-jobs?object_id={object_id}&object_name={object_name}

    Pass both identifiers from step 2 exactly as returned. Choose a sensible polling interval (for example a few seconds, growing over time). Do not tight-loop.

  4. Wait for a terminal state. The response's data.status field will eventually reach one of the terminal values below. Then read the job's progress for counts and its errors for any per-row failures.

Treat the initial 202 as "the request landed," not "the work is done." Only the final job state answers that.

Terminal states

A job's status field advances through in-progress values and settles on one of two terminal outcomes:

  • done - the work finished. Inspect progress for per-row counts (for import jobs: created, updated, failed, skipped, percent; for delete jobs: deleted, not_found, failed, percent) and errors for any per-row failure detail.
  • failed - the work stopped and the outcome is not usable. Read errors and message to decide whether to fix input and re-queue.

Any other value means the job is still running - continue polling.

Concurrency conflicts

Async jobs are subject to concurrency limits, and the exact limits may vary by account and can change over time. If a new job cannot start because concurrency is exhausted, the queuing endpoint returns HTTP 409 Conflict. When that happens:

  • Wait for an in-progress job to reach a terminal state, or otherwise free capacity.
  • Retry the queuing call once capacity is available.
  • Do not tight-loop the queuing endpoint. Aggressive retries won't create capacity and can trip rate limits unnecessarily.

Design your client to react to 409 responses rather than to a hardcoded concurrency number.

Stats refresh exception

Requesting a poptin stats refresh is also asynchronous, but it does not use the bulk-jobs endpoint. Instead, track the refresh on the poptin resource itself:

  • Read refresh_status and last_updated_at from List poptins or Show poptin.
  • Poll those endpoints on an interval until refresh_status returns to idle.
  • Requesting another refresh while one is already in progress may return HTTP 200 idempotently - no new work is queued.

Treat this as an object-level state change on the poptin, not a bulk job.

Related