Skip to content
Looking for UK-domestic placements?See the UK site →

Rate limits and errors

Limits are enforced exactly, per key, per minute. Errors share one shape and one set of codes, so a single handler covers the whole API.

Rate limits

PlanRequests per minute, per key
Pro60
Pro Scale300
Client120
Internal600

The window is a fixed minute and the count is shared across all our servers, so the limit holds even when you send a burst. A request over the limit gets 429 rate_limited with a Retry-After header (seconds) and is not charged. In front of that, our edge applies a coarse per-key and per-IP flood limit that no normal integration reaches.

Response headers

HeaderMeaning
x-request-idUnique id for the request. Quote it to support.
x-credits-chargedCredits this response cost. 0 for free calls, refunds and replays.
Retry-AfterSeconds to wait. Sent with 429 and with 409 request_in_progress.
x-idempotent-replaytrue when the body is a stored replay of an earlier request.

Error format

HTTP/1.1 402 Payment Required
x-request-id: 7f3c…

{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits left this cycle. Top up or upgrade your plan.",
    "requestId": "7f3c…"
  }
}

Branch on error.code, never on the message text, which may be reworded.

Error codes

StatusCodeMeaning and what to do
400invalid_query, invalid_job_description, invalid_role, invalid_id, invalid_url, invalid_events, invalid_kind, invalid_phone, invalid_city, missing_file, unsupported_type, file_too_largeA field is missing or out of range. The message names it and the limit. Fix the request; do not retry unchanged.
400invalid_idempotency_keyIdempotency-Key must be 8 to 200 visible characters.
400test_keyTest keys cannot register webhooks or be billed. Use a live key.
401missing_keyNo Authorization: Bearer header.
401invalid_keyUnknown or malformed key.
402insufficient_creditsNot enough credits this period. Top up, upgrade, or wait for renewal.
402subscription_inactivePeriod ended and grace is over. Renew via /v1/billing/checkout.
403key_disabledKey revoked or suspended. Contact support.
403ip_not_allowedKey is locked to other source addresses.
403missing_scopeThe key lacks the scope in requiredScope.
403test_keyTest keys cannot call chat, resumes or feedback.
404not_foundNo such candidate, role or webhook. Not charged.
405method_not_allowedThe MCP endpoint accepts POST only.
409request_in_progressSame Idempotency-Key is still running. Wait Retry-After seconds.
409too_many_webhooksA key can have at most 5 webhook endpoints. Delete one first.
422idempotency_key_reusedSame Idempotency-Key with a different body. Use a new key.
422unreadable_file, no_textResume could not be read, or has no text (scanned images are not supported). Not charged.
429rate_limitedOver the per-minute limit. Wait Retry-After seconds.
500internal_errorOur fault. Refunded. Safe to retry with the same Idempotency-Key.
503scoring_unavailableMatch or fit scoring is briefly down. Not charged. Retry with backoff.

Retry rules

  • Retry: 429 (after Retry-After), 409 request_in_progress, 500, 502, 503, 504 and network errors. Use exponential backoff with jitter.
  • Do not retry unchanged: 400, 401, 403, 404, 422.
  • Attach an Idempotency-Key to every retried POST so a retry after a timeout cannot charge twice. See Idempotency.
curl
# curl has --retry for transient failures
curl --retry 4 --retry-all-errors https://api.helixhumancapital.in/v1/me -H "Authorization: Bearer $HELIX_KEY"
Python (requests)
import time, random, uuid, requests

def call(path, body, key):
    idem = str(uuid.uuid4())             # one key for all attempts of this logical call
    for attempt in range(5):
        r = requests.post(f"https://api.helixhumancapital.in{path}", json=body,
                          headers={"Authorization": f"Bearer {key}", "Idempotency-Key": idem},
                          timeout=60)
        if r.status_code not in (409, 429, 500, 502, 503, 504):
            return r
        wait = float(r.headers.get("Retry-After", 2 ** attempt)) + random.random()
        time.sleep(wait)
    return r
Node.js (fetch)
async function call(path, body, key) {
  const idem = crypto.randomUUID() // one key for every attempt of this logical call
  for (let attempt = 0; attempt < 5; attempt++) {
    const r = await fetch(`https://api.helixhumancapital.in${path}`, {
      method: "POST",
      headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json", "Idempotency-Key": idem },
      body: JSON.stringify(body),
    })
    if (![409, 429, 500, 502, 503, 504].includes(r.status)) return r
    const wait = Number(r.headers.get("Retry-After") ?? 2 ** attempt) + Math.random()
    await new Promise((res) => setTimeout(res, wait * 1000))
  }
  throw new Error("gave up")
}
Rate limits and errors | Helix API documentation — Helix Human Capital