Skip to main content

Async polling flow

The ZenHire API is asynchronous: submit returns immediately, and you poll for results. This page explains why, and how to design your integration around it.

Why async?​

Speech analysis runs through several steps — transcription, speaker diarization, candidate detection, then three independent ML scoring pipelines (vocabulary, fluency, accent). Typical end-to-end latency is 2–5 minutes, sometimes longer under load. A synchronous HTTP request would time out on most clients.

The async flow lets you:

  • Submit thousands of audio files in parallel without blocking on any one.
  • Survive client-side restarts — the run id never expires.
  • Safely retry a transient network error on the poll endpoint.

The lifecycle​

queued → processing → success | partial | failed
  • queued — the request was accepted but no processing slot was available. The run starts automatically, in FIFO order, when a slot frees up. You see queuePosition in the response.
  • processing — actively running. Retry-After is set on the poll response.
  • success — all three scores produced.
  • partial — at least one score produced, others failed. Credits are charged proportionally.
  • failed — no scores produced. No credits charged. See error.code and error.message.

Use pollIntervalSeconds from the most recent response. If you want to implement your own strategy:

Elapsed since submitInterval
0–2 minutes15 seconds
2–5 minutes30 seconds
5+ minutes60 seconds

Minimum interval per run id is 10 seconds. Faster polls return 429 POLL_RATE_LIMITED — respect the Retry-After header.

When to stop polling​

Stop when the status is terminal (success, partial, or failed), or when your budget expires.

The run id never expires. If your client crashes mid-poll, you can pick up the same id hours later and continue polling.

Concurrency and queueing​

Default 8 simultaneous processing runs per client (contact support to extend). When you reach that limit, new submissions return 202 Accepted with status: "queued" — not an error. Queue position is included in the response.

This means a successful 202 from submit doesn't guarantee your run has started — check the poll endpoint to know when processing begins.

Explainability on the success response​

Successful and partial responses include an optional explainability object with plain-language, human-readable summaries of why each score came out the way it did:

{
"explainability": {
"vocab": "Advanced vocabulary with consistent use of C1+ words...",
"fluency": "Fluid delivery at 156 words/min with a strong..."
}
}

Two sentences per dimension, safe to surface directly to end users (hiring managers, candidates, audit reviewers) — no post-processing required. Omitted on failed runs and in rare cases where a per-dimension feature breakdown couldn't be produced, so branch on presence before rendering.

Poll-only across services​

This submit-and-poll pattern is the common shape across the platform. Speech, CV DeepMatch, and CV DeepSearch all support a poll-only integration: submit returns a run id, and you poll until a terminal state. No public endpoint is required.

The same cadence rules apply to CV DeepMatch and CV DeepSearch — minimum 10 seconds between polls per id, with 429 + Retry-After on faster polls. See the CV DeepMatch integration guide and CV DeepSearch search guide for poll-vs-webhook details.

Webhooks alternative?​

Every module that uses submit + poll also supports an optional signed webhook as a push channel on top of polling — the poll endpoint always keeps working, so a webhook is an addition, never a replacement.

  • Speech API — results are delivered to the webhook endpoint(s) you register in the ZenHire dashboard (events speech.completed / speech.failed), signed with your webhook signing secret. There's no per-request webhook_url; register an endpoint once and every run for that client is delivered. Register nothing to integrate poll-only.
  • CV DeepMatch and CV DeepSearch — supply a webhook_url on submit to also receive a signed HMAC callback. Omit it to integrate poll-only.

See in the API reference​