Jobs
The jobs endpoints manage the full maintenance job lifecycle — submission,
monitoring, cancellation, and deletion. All maintenance work is asynchronous:
you submit a job, receive a 202 Accepted, and poll for completion.
Jobs are stored in the jobs Postgres table. A job_queue table manages
dispatch to workers, and a table_locks table prevents concurrent maintenance
on the same table.
Submit maintenance job
POST /tables/{database}/{table}/maintenanceSubmits a new maintenance job for the given table. The API validates the requested actions, acquires a table lock, persists the job to Postgres, and enqueues it for worker pickup.
Path parameters
| Parameter | Type | Description |
|---|---|---|
database | string | Database name. |
table | string | Table name. |
Request body
{ "actions": ["rewrite_data_files", "expire_snapshots"], "dry_run": false}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
actions | string[] | Yes | — | Maintenance actions to run. At least one required. |
dry_run | boolean | No | false | When true, validates and returns immediately without running. |
Valid actions (always executed in this order):
rewrite_data_filesrewrite_position_delete_filesrewrite_manifestsexpire_snapshotsremove_orphan_files
Response — 202 Accepted
{ "job_id": "a1b2c3d4e5f678901234567890abcdef", "database": "analytics", "table_name": "page_views", "actions": ["rewrite_data_files", "expire_snapshots"], "dry_run": false, "status": "pending", "submitted_at": "2026-04-25T14:35:00.000000+00:00", "started_at": null, "completed_at": null, "results": null, "error": null}Response headers:
| Header | Value | Description |
|---|---|---|
Location | /jobs/{job_id} | URL to poll for job status. |
Retry-After | 30 | Suggested poll interval in seconds. Present for non-dry-run jobs. |
Status codes
| Code | Meaning |
|---|---|
202 | Job accepted and queued (or completed immediately for dry runs). |
400 | Empty actions list or invalid action name. |
404 | Table not found in the table cache. |
409 | Maintenance already in progress for this table (table lock held). |
422 | Invalid database or table name. |
503 | Drain mode is enabled — ingestion is paused for backend migration. |
Example
curl -X POST https://snowpack-api.internal/tables/analytics/page_views/maintenance \ -H "Content-Type: application/json" \ -d '{"actions": ["rewrite_data_files", "expire_snapshots"]}'Dry run (validates without executing):
curl -X POST https://snowpack-api.internal/tables/analytics/page_views/maintenance \ -H "Content-Type: application/json" \ -d '{"actions": ["rewrite_data_files"], "dry_run": true}'List jobs
GET /jobsReturns active jobs from the jobs Postgres table. Use query parameters to
narrow results by table or status.
Query parameters
| Parameter | Type | Description |
|---|---|---|
table | string | Filter by table in database.table_name format. |
status | string | Filter by status: pending, running, completed, failed, or cancelled. |
Response — 200 OK
[ { "job_id": "a1b2c3d4e5f678901234567890abcdef", "database": "analytics", "table_name": "page_views", "actions": ["rewrite_data_files", "expire_snapshots"], "dry_run": false, "status": "running", "submitted_at": "2026-04-25T14:35:00.000000+00:00", "started_at": "2026-04-25T14:35:05.000000+00:00", "completed_at": null, "results": null, "error": null }]Status codes
| Code | Meaning |
|---|---|
200 | Success. |
Example
# All jobscurl https://snowpack-api.internal/jobs
# Only running jobs for a specific tablecurl "https://snowpack-api.internal/jobs?table=analytics.page_views&status=running"Get job
GET /jobs/{job_id}Returns a single job with full details, including per-action results once the job completes.
While the job is pending or running, the response includes a Retry-After: 30 header as a hint for poll-based clients.
Path parameters
| Parameter | Type | Description |
|---|---|---|
job_id | string | The job ID (hex). |
Response — 200 OK
{ "job_id": "a1b2c3d4e5f678901234567890abcdef", "database": "analytics", "table_name": "page_views", "actions": ["rewrite_data_files", "expire_snapshots"], "dry_run": false, "status": "completed", "submitted_at": "2026-04-25T14:35:00.000000+00:00", "started_at": "2026-04-25T14:35:05.000000+00:00", "completed_at": "2026-04-25T14:38:22.000000+00:00", "results": [ { "action": "rewrite_data_files", "success": true, "message": "Compacted 87 files into 12", "error": null, "elapsed_seconds": 142.3 }, { "action": "expire_snapshots", "success": true, "message": "Expired 28 snapshots", "error": null, "elapsed_seconds": 5.1 } ], "error": null}The results array contains one entry per action, each with:
| Field | Type | Description |
|---|---|---|
action | string | Action name. |
success | boolean | Whether the action succeeded. |
message | string | Human-readable summary. |
error | string|null | Error details if the action failed. |
elapsed_seconds | float | Wall-clock time for this action. |
Response headers
| Header | Condition | Value |
|---|---|---|
Retry-After | Job is pending or running | 30 |
Status codes
| Code | Meaning |
|---|---|
200 | Success. |
404 | Job not found. |
Example
curl https://snowpack-api.internal/jobs/a1b2c3d4e5f678901234567890abcdefCancel job
POST /jobs/{job_id}/cancelCancels a pending or running job. This operation runs in a single Postgres
transaction that:
- Acquires a row lock on the job (
SELECT ... FOR UPDATE). - Sets status to
cancelledand revokes the fence token (attempt_id = NULL), so any in-flight worker will no-op on its next write. - Deletes the
job_queuerow to prevent worker pickup. - Releases the
table_locksentry if the job was still pending (not yet claimed by a worker).
For running jobs, the worker detects the fence revocation in its finally block
and releases the table lock at that point.
Path parameters
| Parameter | Type | Description |
|---|---|---|
job_id | string | The job ID (hex). |
Response — 200 OK
Returns the updated job object with status: "cancelled".
{ "job_id": "a1b2c3d4e5f678901234567890abcdef", "database": "analytics", "table_name": "page_views", "actions": ["rewrite_data_files"], "dry_run": false, "status": "cancelled", "submitted_at": "2026-04-25T14:35:00.000000+00:00", "started_at": null, "completed_at": "2026-04-25T14:36:10.000000+00:00", "results": null, "error": "Cancelled by user"}Status codes
| Code | Meaning |
|---|---|
200 | Job cancelled successfully. |
404 | Job not found (or already soft-deleted). |
409 | Job is already in a terminal state (completed, failed, or cancelled). |
Example
curl -X POST https://snowpack-api.internal/jobs/a1b2c3d4e5f678901234567890abcdef/cancelDelete job
DELETE /jobs/{job_id}Soft-deletes a job by setting a deleted_at tombstone. Only terminal jobs
(completed, failed, or cancelled) can be deleted. Active jobs must be
cancelled first.
The job row remains in Postgres for audit purposes but is excluded from normal queries.
Path parameters
| Parameter | Type | Description |
|---|---|---|
job_id | string | The job ID (hex). |
Response — 204 No Content
No response body.
Status codes
| Code | Meaning |
|---|---|
204 | Job soft-deleted. |
404 | Job not found. |
409 | Job is still pending or running. Cancel it first. |
Example
curl -X DELETE https://snowpack-api.internal/jobs/a1b2c3d4e5f678901234567890abcdef