--- title: Request Status and Polling | SafetyKit description: Track request progress and retrieve request-level outputs. --- While webhooks are the primary completion mechanism, polling is useful when: - validating integration health - running backfills or migrations - reconciling missed webhook deliveries Use [`GET /v1/data/{namespace}/requests/{requestId}`](/api/resources/data/methods/getStatus/index.md) for both checking for request status and retreiving request-level outputs. You can poll this endpoint during long-running imports and stop as soon as the request reaches a terminal state. ## Checking request status Endpoint: [`GET /v1/data/{namespace}/requests/{requestId}`](/api/resources/data/methods/getStatus/index.md) Example response: ``` { "namespace": "products", "request_id": "req_01h2m7qdmdjckc30e1mnq6xqfd", "status": "ingesting", "data": [] } ``` The `data` attribute is always empty while data is being ingested and processed (i.e., `status != "succeeded"`). `status` can be one of `queued`, `uploading` (import only), `ingesting`, `succeeded`, and `failed`. ## Retrieve request outputs Endpoint: [`GET /v1/data/{namespace}/requests/{requestId}`](/api/resources/data/methods/getStatus/index.md) Example response shape for [add](/api/resources/data/methods/add/index.md) requests: ``` { "namespace": "products", "request_id": "req_01h2m7qdmdjckc30e1mnq6xqfd", "status": "succeeded", "data": [ { "id": "product_12345", "output": { "actions": [], "labels": [{ "label": "policy.example_violation" }], "fields": {} }, "metadata": {}, "url": "https://app.safetykit.com/..." } ] } ``` For [import](/api/resources/data/methods/import/index.md) requests, you’ll receive a temporary download URL instead of inline `data`: ``` { "namespace": "products", "request_id": "req_01h2m7qdmdjckc30e1mnq6xqfd", "status": "succeeded", "data_url": "https://s3.amazonaws.com/...", "data_count": 1200, "data_expires_at": "2026-01-15T12:30:00.000Z" } ``` ## Downloaded JSONL result shape For import requests, the `data_url` file is JSONL with one result object per line. Each line follows the same structure as request-level object outputs in the `data` array: ``` {"id":"product_12345","output":{"actions":[],"labels":[{"label":"policy.example_violation"}],"fields":{}},"metadata":{"source":"backfill"},"url":"https://app.safetykit.com/..."} {"id":"product_67890","output":{"actions":[{"action":"manual_review","queue":"high_risk"}],"labels":[],"fields":{}},"metadata":{},"url":"https://app.safetykit.com/..."} ``` When parsing this file: - read line by line (streaming), not as one large JSON document - treat `metadata` as optional - treat `output` as potentially nullable - map rows to your original content with `id` * [ TypeScript](#tab-panel-22) * [ Python](#tab-panel-23) ``` import readline from "node:readline"; import { createReadStream } from "node:fs"; async function parseResultJsonl(path: string) { const rl = readline.createInterface({ input: createReadStream(path), crlfDelay: Infinity, }); for await (const line of rl) { if (!line.trim()) continue; const row = JSON.parse(line); await upsertResult(row.id, row.output, row.metadata ?? {}, row.url); } } ``` ``` import json def parse_result_jsonl(path: str): with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue row = json.loads(line) upsert_result( row["id"], row.get("output"), row.get("metadata", {}), row["url"], ) ``` ## Polling loop guidance - poll every 3-10 seconds - stop polling when status is `succeeded` or `failed` - treat polling as a complement, not replacement, for webhooks See [Copy-and-Paste Quickstart](/using-data-api/copy-and-paste-quickstart/index.md) for copy-paste polling and webhook examples.