> ## Documentation Index
> Fetch the complete documentation index at: https://docs.filerouter.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# GET /api/v1/jobs/{jobId} — Retrieve Job Status and Result

> GET /api/v1/jobs/{jobId} polls job status and returns the parsed result when complete. Includes response shapes for all statuses and polling examples.

The `GET /api/v1/jobs/{jobId}` endpoint lets you check the status of a job and retrieve its result once parsing completes. Because FileRouter processes documents asynchronously, you call this endpoint repeatedly until the job transitions to `complete` or `failed`. The response shape changes depending on the job's current status.

```
GET https://filerouter.dev/api/v1/jobs/{jobId}
```

<Tip>
  The TypeScript `FileRouterClient` polls this endpoint automatically and resolves its promise when the job completes or rejects it when the job fails. You only need to poll manually when using the REST API directly from a non-TypeScript environment.
</Tip>

## Path Parameters

<ParamField path="jobId" type="string" required>
  The UUID of the job to retrieve. You get this value from the `id` field in the `POST /api/v1/jobs` response. Example: `550e8400-e29b-41d4-a716-446655440000`.
</ParamField>

## Required Headers

<ParamField header="Authorization" type="string" required>
  Bearer token authentication. Format: `Bearer <your-api-key>`. See [Authentication](/api/authentication) for details.
</ParamField>

## Response Shapes by Status

The response body varies based on the job's current status. Poll until you see `"complete"` or `"failed"`.

<Tabs>
  <Tab title="queued / running">
    The job is in the queue or actively being processed. Continue polling.

    ```json title="In-progress job response" theme={null}
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "queued"
    }
    ```

    <ResponseField name="id" type="string" required>
      The UUID of the job.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Either `"queued"` (waiting to start) or `"running"` (currently processing).
    </ResponseField>
  </Tab>

  <Tab title="complete">
    The job finished successfully. The shape of the `result` field depends on whether the job was a `parse` or `compare` operation.

    #### Parse job result (`ParseResult`)

    ```json title="Completed parse job response" theme={null}
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "complete",
      "result": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "outputs": {
          "markdown": "# Annual Report 2024\n\nThis report covers..."
        },
        "pageCount": 12,
        "provider": "llamaparse",
        "timing": {
          "startedAt": "2024-01-15T10:00:00.000Z",
          "completedAt": "2024-01-15T10:00:04.500Z",
          "durationMs": 4500
        },
        "warnings": []
      }
    }
    ```

    <ResponseField name="id" type="string" required>
      The UUID of the job.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Always `"complete"` in this response shape.
    </ResponseField>

    <ResponseField name="result" type="object" required>
      The parse result object. Present only when `status` is `"complete"` and the job operation was `"parse"`.

      <Expandable title="ParseResult fields">
        <ResponseField name="result.id" type="string" required>
          The job ID.
        </ResponseField>

        <ResponseField name="result.outputs" type="object" required>
          A map of output format IDs to their normalized values. The keys match the `outputs` array you specified when creating the job (e.g., `{ "markdown": "..." }`).
        </ResponseField>

        <ResponseField name="result.pageCount" type="number" required>
          The number of pages processed in the document.
        </ResponseField>

        <ResponseField name="result.provider" type="string" required>
          The provider ID that processed the document (e.g., `"llamaparse"`, `"mistral-ocr"`).
        </ResponseField>

        <ResponseField name="result.timing" type="object" required>
          Timing information for the parse operation, with `startedAt`, `completedAt` (ISO 8601 strings), and `durationMs` (number).
        </ResponseField>

        <ResponseField name="result.warnings" type="array" required>
          Array of any warnings produced during parsing. Empty when there are none.
        </ResponseField>

        <ResponseField name="result.quality" type="object">
          Optional provider-native quality score with a `score` number and optional `scale`.
        </ResponseField>

        <ResponseField name="result.raw" type="unknown">
          The full raw provider response. Only present when `includeRaw` was `true` when creating the job.
        </ResponseField>
      </Expandable>
    </ResponseField>

    #### Compare job result (`CompareResult`)

    When the job operation was `"compare"`, the `result` field contains a `CompareResult` — not a `ParseResult`. The key difference is the `providers` array, which holds one entry per provider that was run, each containing that provider's individual `ParseResult`.

    ```json title="Completed compare job response" theme={null}
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "complete",
      "result": {
        "input": "https://example.com/annual-report-2024.pdf",
        "outputs": ["markdown"],
        "providers": [
          {
            "provider": "llamaparse",
            "status": "parsed",
            "durationMs": 4200,
            "result": {
              "id": "550e8400-e29b-41d4-a716-446655440000",
              "outputs": { "markdown": "# Annual Report 2024\n\n..." },
              "pageCount": 12,
              "provider": "llamaparse",
              "timing": {
                "startedAt": "2024-01-15T10:00:00.000Z",
                "completedAt": "2024-01-15T10:00:04.200Z",
                "durationMs": 4200
              },
              "warnings": []
            }
          },
          {
            "provider": "mistral-ocr",
            "status": "parsed",
            "durationMs": 3100,
            "result": {
              "id": "550e8400-e29b-41d4-a716-446655440000",
              "outputs": { "markdown": "# Annual Report 2024\n\n..." },
              "pageCount": 12,
              "provider": "mistral-ocr",
              "timing": {
                "startedAt": "2024-01-15T10:00:00.000Z",
                "completedAt": "2024-01-15T10:00:03.100Z",
                "durationMs": 3100
              },
              "warnings": []
            }
          }
        ],
        "timing": {
          "startedAt": "2024-01-15T10:00:00.000Z",
          "completedAt": "2024-01-15T10:00:04.200Z",
          "durationMs": 4200
        }
      }
    }
    ```

    <ResponseField name="result" type="object" required>
      The compare result object. Present only when `status` is `"complete"` and the job operation was `"compare"`.

      <Expandable title="CompareResult fields">
        <ResponseField name="result.input" type="string" required>
          The source document URL or identifier that was processed.
        </ResponseField>

        <ResponseField name="result.outputs" type="array" required>
          The list of output format IDs that were requested (e.g., `["markdown"]`).
        </ResponseField>

        <ResponseField name="result.providers" type="array" required>
          One entry per provider that was run. Each entry has:

          * `provider` (string) — the provider ID
          * `status` (`"parsed"` | `"failed"` | `"unsupported"`) — whether this provider succeeded
          * `durationMs` (number) — how long this provider took
          * `result` (ParseResult, optional) — the provider's parse result, present when `status` is `"parsed"`
          * `error` (object, optional) — error details with `message` and optional `code`, present when `status` is `"failed"`
        </ResponseField>

        <ResponseField name="result.timing" type="object" required>
          Overall timing for the compare operation, with `startedAt`, `completedAt` (ISO 8601 strings), and `durationMs` (number).
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Tab>

  <Tab title="failed">
    The job encountered an error during processing. The `error` field contains a description of what went wrong.

    ```json title="Failed job response" theme={null}
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "failed",
      "error": "Provider returned an unexpected response."
    }
    ```

    <ResponseField name="id" type="string" required>
      The UUID of the job.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Always `"failed"` in this response shape.
    </ResponseField>

    <ResponseField name="error" type="string">
      A description of the error that caused the job to fail. May be `null` if the failure reason is unavailable.
    </ResponseField>
  </Tab>
</Tabs>

<Warning>
  Results expire after **7 days**. Fetching a job whose result has expired returns HTTP `410 Gone`. Download or store your results before the expiry window if you need them long-term.
</Warning>

## Polling Example

Poll every few seconds until the job reaches a terminal status (`complete` or `failed`):

```bash title="Polling loop (bash)" theme={null}
JOB_ID="550e8400-e29b-41d4-a716-446655440000"

while true; do
  RESPONSE=$(curl --silent \
    --header "Authorization: Bearer $FILEROUTER_API_KEY" \
    "https://filerouter.dev/api/v1/jobs/$JOB_ID")

  STATUS=$(echo "$RESPONSE" | jq -r '.status')
  echo "Status: $STATUS"

  if [ "$STATUS" = "complete" ]; then
    echo "Result:"
    echo "$RESPONSE" | jq '.result'
    break
  elif [ "$STATUS" = "failed" ]; then
    echo "Job failed: $(echo "$RESPONSE" | jq -r '.error')"
    exit 1
  fi

  sleep 2
done
```

## Error Responses

<Accordion title="401 Unauthorized">
  The `Authorization` header is missing, invalid, or the key has been disabled or expired. See [Authentication](/api/authentication) for details.

  ```json title="401 error body" theme={null}
  {
    "status": 401,
    "code": "unauthorized",
    "detail": "Missing FileRouter API key.",
    "title": "Unauthorized",
    "type": "https://filerouter.dev/problems/unauthorized",
    "instance": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000",
    "request_id": "01J2Y9QX3MXJQHD2YQ9N7J93M4"
  }
  ```
</Accordion>

<Accordion title="404 Not Found">
  No job with the given `jobId` exists for your account. Verify the UUID and ensure you're using the same API key that created the job.

  ```json title="404 error body" theme={null}
  {
    "status": 404,
    "code": "not_found",
    "detail": "No job found with the given ID.",
    "title": "Not Found",
    "type": "https://filerouter.dev/problems/not-found",
    "instance": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000",
    "request_id": "01J2Y9QX3MXJQHD2YQ9N7J93M4"
  }
  ```
</Accordion>

<Accordion title="410 Gone — Result expired">
  The job completed, but its result has passed the 7-day retention window and has been deleted. You can no longer retrieve the result for this job ID.

  ```json title="410 error body" theme={null}
  {
    "status": 410,
    "code": "result_expired",
    "detail": "The result for this job has expired and is no longer available.",
    "title": "Gone",
    "type": "https://filerouter.dev/problems/result-expired",
    "instance": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000",
    "request_id": "01J2Y9QX3MXJQHD2YQ9N7J93M4"
  }
  ```
</Accordion>

<Accordion title="500 Internal Server Error">
  An unexpected server-side error occurred. The response includes a `request_id` — include it when contacting support.
</Accordion>
