Error Codes

Error Codes

All API errors follow a consistent format:

{
  "error": {
    "type": "validation_error",
    "message": "description is required",
    "request_id": "req_01JQ...",
    "details": [
      { "field": "description", "message": "required" }
    ]
  }
}

Error Types

TypeHTTP StatusDescription
validation_error400Request body or parameters are invalid
authentication_error401Missing or invalid API key
authorization_error403Key lacks required permissions
not_found404Resource does not exist
conflict409Resource state conflict (e.g., cancelling a completed job)
idempotency_error409Idempotency key reused with different parameters
rate_limit_exceeded429Too many requests — check Retry-After header
insufficient_credits402Not enough credits to post the job
free_tier_job_limit403Free-tier organization hit the concurrent-job cap (default 1 active job) — upgrade or wait for current job to finish
pricing_violation400price_per_file_cents is outside the platform-allowed band ($0.10–$500.00) or below the minimum-wage floor for the requested format
transport_error502An upstream provider call (e.g., S3, Stripe, scoring) failed with a transport-level fault — retryable
forbidden403Key is valid but the target resource is explicitly denied (e.g., internal-only endpoint, org mismatch)
unauthorized401Authentication challenge required (e.g., a provisioning endpoint called without the service token)
internal_error500Unexpected server error
bad_gateway502Upstream service unavailable
service_unavailable503API temporarily overloaded
gateway_timeout504Upstream service timeout

Handling Errors

Retryable Errors

These errors are safe to retry with exponential backoff:

  • 429 rate_limit_exceeded — Wait for Retry-After header duration
  • 500 internal_error — Retry up to 3 times
  • 502 transport_error / 502 bad_gateway — Retry up to 3 times with backoff
  • 503 service_unavailable / 504 gateway_timeout — Retry up to 3 times with backoff

Non-Retryable Errors

These require fixing the request:

  • 400 validation_error — Fix request body
  • 400 pricing_violation — Adjust price_per_file_cents into the allowed band
  • 401 authentication_error / unauthorized — Check API key
  • 402 insufficient_credits — Purchase more credits
  • 403 authorization_error / forbidden — Key lacks the required scope, or the resource is off-limits
  • 403 free_tier_job_limit — Finish or cancel your active job, or upgrade off the free tier
  • 404 not_found — Check resource ID
  • 409 conflict / idempotency_error — Check resource state or use a fresh idempotency key

SDK Error Handling

Both SDKs automatically retry retryable errors with exponential backoff.

import { FirstHandApiError, FirstHandRateLimitError } from '@firsthandapi/sdk';
 
try {
  await client.createJob({...});
} catch (e) {
  if (e instanceof FirstHandRateLimitError) {
    console.log(e.retryAfter);  // seconds to wait
  } else if (e instanceof FirstHandApiError) {
    console.log(e.type);       // "validation_error"
    console.log(e.status);     // 400
    console.log(e.requestId);  // "req_01JQ..."
    console.log(e.retryable);  // false
  }
}
from firsthandapi import FirstHandApiError, FirstHandRateLimitError
 
try:
    client.create_job({...})
except FirstHandRateLimitError as e:
    print(e.retry_after)   # seconds to wait
except FirstHandApiError as e:
    print(e.type)          # "validation_error"
    print(e.status)        # 400
    print(e.request_id)    # "req_01JQ..."
    print(e.retryable)     # False