Skip to content
Last updated

Job Requeue API

Gensail allows you to requeue jobs that were automatically skipped due to processing filters. This enables you to adjust filter settings and reprocess calls without re-ingesting them.

Overview

When a job is created, it enters the processing pipeline. Before transcription begins, the worker validates the job against processing filters configured for your workspace. If a job doesn't meet the filter criteria (e.g., call duration is too short), it's marked as skipped instead of being processed.

Skipped jobs remain in the system and can be requeued at any time. When requeued, the job is re-validated against current filter settings, allowing you to:

  1. Lower filter thresholds and reprocess previously skipped calls
  2. Retry calls after fixing configuration issues
  3. Batch requeue calls after bulk filter changes

Job Lifecycle

         ┌─────────────────────────────────────────┐
         │                                         │
         ▼                                         │
      queued ───► transcribing ───► analyzing ───► publishing ───► completed

         │ (filter validation fails)

      skipped ───────────────────────────────────────────────────────────►
                          │                                              │
                          └──────────── requeue (API) ───────────────────┘

Processing Filters

Processing filters are evaluated by the worker before transcription. Currently supported filters:

FilterConfig PathDefaultDescription
min_duration_secondsprocessing.filters.min_duration_seconds30Minimum call duration in seconds

Filters are inherited through the configuration hierarchy:

  • Platform defaults (30 seconds)
  • Partner configuration
  • Organization configuration
  • Workspace configuration

Requeuing Jobs

Endpoint

PATCH /api/v1/jobs/{job_id}/status

Request

{
  "status": "queued"
}

Response

{
  "job_id": "12345",
  "previous_status": "skipped",
  "new_status": "queued",
  "message": "Job requeued successfully (requeue #1). Job will be re-validated against current processing filters."
}

Authorization

Requires an API key with access to the job's workspace:

  • Workspace-scoped API keys can requeue jobs in their workspace
  • Organization-scoped API keys can requeue jobs in any workspace within the organization
  • Partner-scoped API keys can requeue jobs in any workspace under the partner

Error Responses

StatusReason
400 Bad RequestInvalid status (only queued supported)
400 Bad RequestJob is not in skipped status
403 ForbiddenNo access to job's workspace
404 Not FoundJob not found

Code Examples

Python

import httpx

async def requeue_job(api_key: str, job_id: int) -> dict:
    """Requeue a skipped job."""
    async with httpx.AsyncClient() as client:
        response = await client.patch(
            f"https://analytics-api.gensail.com/api/v1/jobs/{job_id}/status",
            headers={"Authorization": f"Bearer {api_key}"},
            json={"status": "queued"},
        )
        response.raise_for_status()
        return response.json()

# Usage
result = await requeue_job("gsk_xxx", 12345)
print(f"Job {result['job_id']} requeued: {result['message']}")

cURL

curl -X PATCH "https://analytics-api.gensail.com/api/v1/jobs/12345/status" \
  -H "Authorization: Bearer gsk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"status": "queued"}'

Bulk Requeue

To requeue multiple skipped jobs, first list them using the jobs endpoint:

# Get all skipped jobs for a workspace
curl "https://analytics-api.gensail.com/api/v1/jobs?status=skipped&workspace_id=xxx" \
  -H "Authorization: Bearer gsk_xxx"

Then iterate and requeue each job:

async def bulk_requeue_skipped(api_key: str, workspace_id: str):
    """Requeue all skipped jobs for a workspace."""
    async with httpx.AsyncClient() as client:
        # List skipped jobs
        response = await client.get(
            "https://analytics-api.gensail.com/api/v1/jobs",
            headers={"Authorization": f"Bearer {api_key}"},
            params={"status": "skipped", "workspace_id": workspace_id},
        )
        response.raise_for_status()
        jobs = response.json()["jobs"]

        # Requeue each job
        results = []
        for job in jobs:
            result = await requeue_job(api_key, int(job["job_id"]))
            results.append(result)

        return results

Metadata Tracking

When a job is requeued, the following metadata is recorded in source_metadata:

FieldDescription
requeue_countNumber of times this job has been requeued
last_requeued_atISO 8601 timestamp of most recent requeue
last_requeued_byScope of API key used for requeue (workspace/organization/partner)
previous_skipped_reasonThe skip reason before requeue
previous_skipped_atWhen the job was previously skipped

Rerun Supersession

POST /api/v1/jobs/{job_id}/retry with mode=rerun creates a child job (parent_job_id set) marked source_metadata.supersedes_parent: true. A superseding child is a reprocessing of the same call, not a new call:

  • GET /api/v1/calls/listing shows ONE row — the parent (original received_at, original source ids) — carrying the newest completed grading run across the family. The child never appears as its own row. Searching by the child's job id resolves to the parent row.
  • GET /api/v1/jobs/{root_id}/runs includes the children's runs, and the top-level transcription fields come from the effective family job — the member owning the selected grading run (newest completed dental_grading_* across the family, the exact /calls/listing rule; families without a completed grading run fall back to the newest completed run of any algorithm) — with explicit effective_job_id + root_job_status provenance. Requesting a child id directly returns only that child's runs (audit access).
  • /stats/feedback-trends and /stats/duration-distribution count the call once; a failed root recovered by a successful superseding child counts as processed. Operational job metrics (/stats, /stats/daily, /stats/by-organization) and billing (/stats/trends) deliberately count physical jobs, children included.
  • Family depth is one level. Rerunning a job that is itself a child attaches the new job to the family ROOT (no grandchildren); the immediate source is recorded in source_metadata.rerun_parent_job_id.
  • Non-superseding children — on-demand jobs from POST /jobs/{id}/run-algorithms (supersedes_parent: false) and legacy children created before this feature — keep their own listing row and never replace the original call's result.

Common Skip Reasons

When a job is skipped, the skipped_reason field indicates why:

ReasonSourceDescription
skip_all_on_ingestPollerAll calls are skipped when skip_all_on_ingest: true is configured
rate_limitPollerRate limits (max_calls_per_workspace or max_total_calls) were exceeded
duration_below_minimum:{actual}<{min}WorkerCall duration is shorter than configured minimum

Skip-All-On-Ingest Mode

When skip_all_on_ingest: true is configured in the CallRail integration settings, ALL calls are created with status skipped regardless of rate limits. This is useful for:

  • Loading all historical calls for manual review
  • Bulk ingestion without automatic processing
  • Allowing selective processing via the API

Configuration:

{
  "integrations": {
    "callrail": {
      "skip_all_on_ingest": true
    }
  }
}

Workflow:

  1. Poller ingests all calls as skipped with reason skip_all_on_ingest
  2. Review calls in your application or via GET /jobs?status=skipped
  3. Selectively queue jobs for processing via PATCH /jobs/{job_id}/status
  4. Worker picks up queued jobs and applies processing filters

Re-validation Behavior

When a requeued job is picked up by a worker:

  1. The job is re-validated against current processing filters
  2. If filters pass, processing continues to transcription
  3. If filters still fail, the job is marked as skipped again

This means:

  • If you haven't changed filter settings, the job will be skipped again
  • To process previously skipped calls, lower the filter threshold first
  • Requeue count tracks how many times a job has been requeued

Best Practices

  1. Adjust filters before bulk requeue: Lower min_duration_seconds before requeuing to avoid immediate re-skip
  2. Monitor requeue counts: High requeue counts may indicate configuration issues
  3. Use bulk operations carefully: Rate limiting applies to API requests
  4. Check job status after requeue: Jobs may be skipped again if filters haven't changed