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

# Poll Job

> Check the status and retrieve results of an asynchronous job 
(submitted via any endpoint with `async: true`).


## Overview

Check the status and retrieve results of an asynchronous job (e.g., submitted via `/extract` with `async: true`).

Poll this endpoint periodically until the job reaches a terminal state (`completed`, `failed`, or `canceled`).

## Response

The response includes job metadata and, when completed, the full extraction results.

```json theme={null}
{
  "job_id": "abc123-def456-ghi789",
  "status": "completed",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:31:45Z",
  "result": {
    "markdown": "# Document Title\n\nExtracted content...",
    "page_count": 15,
    "bounding_boxes": { ... },
    "plan-info": { ... }
  }
}
```

### Response Fields

| Field        | Type   | Description                                                                                                                         |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `job_id`     | string | Unique identifier for the extraction job.                                                                                           |
| `status`     | string | Current job status: `pending`, `processing`, `completed`, `failed`, or `canceled`.                                                  |
| `created_at` | string | ISO 8601 timestamp when the job was submitted.                                                                                      |
| `updated_at` | string | ISO 8601 timestamp of the last status update.                                                                                       |
| `result`     | object | Extraction output (only present when `status` is `completed`). See [Extract](/api-reference/endpoint/extract) for result structure. |
| `error`      | string | Error message (only present when `status` is `failed`).                                                                             |

### Job Status Values

| Status       | Description                                                             |
| ------------ | ----------------------------------------------------------------------- |
| `pending`    | Job is queued and waiting to be processed.                              |
| `processing` | Job is currently being processed.                                       |
| `completed`  | Job finished successfully. Results are available in the `result` field. |
| `failed`     | Job encountered an error. See `error` field for details.                |
| `canceled`   | Job was canceled before completion.                                     |

## Polling Strategy

We recommend polling with exponential backoff:

<CodeGroup>
  ```python Python SDK theme={null}
  from pulse import Pulse
  import time

  client = Pulse(api_key="YOUR_API_KEY")

  def poll_job(job_id: str, max_attempts: int = 60):
      """
      Poll for job completion with exponential backoff.
      
      Args:
          job_id: The job ID returned from /extract with async: true
          max_attempts: Maximum number of polling attempts
          
      Returns:
          The extraction result when job completes
      """
      delay = 1  # Start with 1 second
      
      for attempt in range(max_attempts):
          # Get job status using the SDK
          response = client.jobs.get_job(job_id=job_id)
          
          if response.status == "completed":
              return response.result
          elif response.status == "failed":
              raise Exception(f"Job failed: {response.error}")
          elif response.status == "canceled":
              raise Exception("Job was canceled")
          
          # Still pending or processing - wait and retry
          print(f"Status: {response.status}, waiting {delay}s...")
          time.sleep(delay)
          delay = min(delay * 1.5, 10)  # Cap at 10 seconds
      
      raise Exception("Polling timeout")

  # Example usage
  job_id = "abc123-def456-ghi789"
  result = poll_job(job_id)
  print(f"Extraction complete! Markdown: {result['markdown'][:100]}...")
  ```

  ```typescript TypeScript SDK theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const client = new PulseClient({
      apiKey: 'YOUR_API_KEY'
  });

  async function pollJob(jobId: string, maxAttempts: number = 60): Promise<any> {
      /**
       * Poll for job completion with exponential backoff.
       */
      let delay = 1; // Start with 1 second
      
      for (let attempt = 0; attempt < maxAttempts; attempt++) {
          // Get job status using the SDK
          const response = await client.jobs.getJob({ jobId });
          
          if (response.status === "completed") {
              return response.result;
          } else if (response.status === "failed") {
              throw new Error(`Job failed: ${response.error}`);
          } else if (response.status === "canceled") {
              throw new Error("Job was canceled");
          }
          
          // Still pending or processing - wait and retry
          console.log(`Status: ${response.status}, waiting ${delay}s...`);
          await new Promise(resolve => setTimeout(resolve, delay * 1000));
          delay = Math.min(delay * 1.5, 10); // Cap at 10 seconds
      }
      
      throw new Error("Polling timeout");
  }

  // Example usage
  const jobId = "abc123-def456-ghi789";
  pollJob(jobId).then(result => {
      console.log(`Extraction complete! Markdown: ${result.markdown?.slice(0, 100)}...`);
  });
  ```

  ```bash curl theme={null}
  #!/bin/bash

  JOB_ID="abc123-def456-ghi789"
  API_KEY="YOUR_API_KEY"
  MAX_ATTEMPTS=60
  DELAY=1

  for ((attempt=1; attempt<=MAX_ATTEMPTS; attempt++)); do
      response=$(curl -s "https://api.runpulse.com/job/${JOB_ID}" \
          -H "x-api-key: ${API_KEY}")
      
      status=$(echo "$response" | jq -r '.status')
      
      case "$status" in
          "completed")
              echo "Job completed!"
              echo "$response" | jq '.result'
              exit 0
              ;;
          "failed")
              error=$(echo "$response" | jq -r '.error')
              echo "Job failed: $error"
              exit 1
              ;;
          "canceled")
              echo "Job was canceled"
              exit 1
              ;;
          *)
              echo "Status: $status, waiting ${DELAY}s... (attempt $attempt)"
              sleep $DELAY
              DELAY=$(echo "$DELAY * 1.5" | bc)
              if (( $(echo "$DELAY > 10" | bc -l) )); then
                  DELAY=10
              fi
              ;;
      esac
  done

  echo "Polling timeout"
  exit 1
  ```
</CodeGroup>

## Example Usage

### Check Job Status

<CodeGroup>
  ```python Python SDK theme={null}
  from pulse import Pulse

  client = Pulse(api_key="YOUR_API_KEY")

  # Check job status
  job_id = "abc123-def456-ghi789"
  response = client.jobs.get_job(job_id=job_id)

  print(f"Job ID: {response.job_id}")
  print(f"Status: {response.status}")
  print(f"Created: {response.created_at}")

  if response.status == "completed":
      print(f"Markdown: {response.result['markdown'][:100]}...")
  elif response.status == "failed":
      print(f"Error: {response.error}")
  ```

  ```typescript TypeScript SDK theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const client = new PulseClient({
      apiKey: 'YOUR_API_KEY'
  });

  // Check job status
  const jobId = "abc123-def456-ghi789";
  const response = await client.jobs.getJob({ jobId });

  console.log(`Job ID: ${response.job_id}`);
  console.log(`Status: ${response.status}`);
  console.log(`Created: ${response.created_at}`);

  if (response.status === "completed") {
      console.log(`Markdown: ${response.result?.markdown?.slice(0, 100)}...`);
  } else if (response.status === "failed") {
      console.log(`Error: ${response.error}`);
  }
  ```

  ```bash curl theme={null}
  # Check job status
  curl https://api.runpulse.com/job/abc123-def456-ghi789 \
    -H "x-api-key: YOUR_API_KEY"
  ```
</CodeGroup>

### Complete Async Workflow

<CodeGroup>
  ```python Python SDK theme={null}
  from pulse import Pulse
  import time
  import json

  client = Pulse(api_key="YOUR_API_KEY")

  # Step 1: Submit async extraction
  print("Submitting async extraction...")
  submit_response = client.extract(
      file_url="https://www.impact-bank.com/user/file/dummy_statement.pdf",
      async_=True
  )

  job_id = submit_response.job_id
  print(f"Job submitted: {job_id}")

  # Step 2: Poll for completion
  delay = 1
  while True:
      status_response = client.jobs.get_job(job_id=job_id)
      
      if status_response.status == "completed":
          print("Extraction complete!")
          extraction_id = status_response.result["extraction_id"]
          print(f"Extraction ID: {extraction_id}")
          break
      elif status_response.status in ["failed", "canceled"]:
          print(f"Job ended with status: {status_response.status}")
          break
      
      print(f"Status: {status_response.status}")
      time.sleep(delay)
      delay = min(delay * 1.5, 10)

  # Step 3 (optional): Apply schema via /schema endpoint
  schema_result = client.schema(
      extraction_id=extraction_id,
      schema_config={
          "input_schema": {
              "type": "object",
              "properties": {
                  "account_holder": {"type": "string"},
                  "balance": {"type": "number"}
              }
          }
      }
  )
  print(f"Schema output: {schema_result.schema_output}")
  ```

  ```typescript TypeScript SDK theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const client = new PulseClient({
      apiKey: 'YOUR_API_KEY'
  });

  // Step 1: Submit async extraction
  console.log("Submitting async extraction...");
  const submitResponse = await client.extract({
      fileUrl: "https://www.impact-bank.com/user/file/dummy_statement.pdf",
      async: true
  });

  const jobId = submitResponse.job_id;
  console.log(`Job submitted: ${jobId}`);

  // Step 2: Poll for completion
  let delay = 1;
  let extractionId: string;
  while (true) {
      const statusResponse = await client.jobs.getJob({ jobId });
      
      if (statusResponse.status === "completed") {
          console.log("Extraction complete!");
          extractionId = statusResponse.result?.extraction_id;
          console.log(`Extraction ID: ${extractionId}`);
          break;
      } else if (statusResponse.status === "failed" || statusResponse.status === "canceled") {
          console.log(`Job ended with status: ${statusResponse.status}`);
          break;
      }
      
      console.log(`Status: ${statusResponse.status}`);
      await new Promise(resolve => setTimeout(resolve, delay * 1000));
      delay = Math.min(delay * 1.5, 10);
  }

  // Step 3 (optional): Apply schema via /schema endpoint
  const schemaResult = await client.schema({
      extraction_id: extractionId,
      schema_config: {
          input_schema: {
              type: "object",
              properties: {
                  account_holder: { type: "string" },
                  balance: { type: "number" }
              }
          }
      }
  });
  console.log(`Schema output:`, schemaResult.schema_output);
  ```

  ```bash curl theme={null}
  # Step 1: Submit async extraction
  JOB_RESPONSE=$(curl -s -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file_url=https://www.impact-bank.com/user/file/dummy_statement.pdf" \
    -F "async=true")

  JOB_ID=$(echo "$JOB_RESPONSE" | jq -r '.job_id')
  echo "Job submitted: $JOB_ID"

  # Step 2: Poll for completion
  DELAY=1
  while true; do
      STATUS_RESPONSE=$(curl -s "https://api.runpulse.com/job/${JOB_ID}" \
          -H "x-api-key: YOUR_API_KEY")
      
      STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status')
      
      case "$STATUS" in
          "completed")
              echo "Extraction complete!"
              EXTRACTION_ID=$(echo "$STATUS_RESPONSE" | jq -r '.result.extraction_id')
              echo "Extraction ID: $EXTRACTION_ID"
              break
              ;;
          "failed"|"canceled")
              echo "Job ended with status: $STATUS"
              break
              ;;
          *)
              echo "Status: $STATUS"
              sleep $DELAY
              DELAY=$(echo "$DELAY * 1.5" | bc)
              if (( $(echo "$DELAY > 10" | bc -l) )); then
                  DELAY=10
              fi
              ;;
      esac
  done
  ```
</CodeGroup>

<Note>
  For webhook-based notifications instead of polling, see the [Webhooks](/api-reference/endpoint/webhook) documentation.
</Note>


## OpenAPI

````yaml GET /job/{jobId}
openapi: 3.1.0
info:
  title: Pulse API
  description: >-
    Production-grade document extraction service that transforms complex
    documents  into structured, AI-ready data. This specification is the single
    source of truth  for the Pulse extraction APIs.
  version: 1.0.0
  contact:
    name: Pulse Support
    email: support@trypulse.ai
    url: https://docs.runpulse.com
servers:
  - url: https://api.runpulse.com
    description: Production server
security:
  - ApiKeyAuth: []
paths:
  /job/{jobId}:
    get:
      tags:
        - Jobs
      summary: Get Job Status
      description: |
        Check the status and retrieve results of an asynchronous job 
        (submitted via any endpoint with `async: true`).
      operationId: getJob
      parameters:
        - name: jobId
          in: path
          required: true
          description: Identifier returned from an async job submission.
          schema:
            type: string
      responses:
        '200':
          description: Current job status payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobStatusResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      x-codeSamples:
        - lang: python
          label: Python SDK
          source: >
            from pulse import Pulse


            client = Pulse(api_key="YOUR_API_KEY")


            status = client.jobs.get_job(job_id="your-job-id")

            print(status.status)   # "pending" | "processing" | "completed" |
            "failed"

            if status.status == "completed":
                print(status.result)
        - lang: typescript
          label: TypeScript SDK
          source: >
            import { PulseClient } from "pulse-ts-sdk";


            const client = new PulseClient({
                apiKey: "YOUR_API_KEY"
            });


            const status = await client.jobs.getJob({ jobId: "your-job-id" });

            console.log(status.status); // "pending" | "processing" |
            "completed" | "failed"

            if (status.status === "completed") {
                console.log(status.result);
            }
        - lang: bash
          label: curl
          source: |
            curl https://api.runpulse.com/job/your-job-id \
              -H "x-api-key: YOUR_API_KEY"
components:
  schemas:
    JobStatusResponse:
      type: object
      description: Current status and metadata for an asynchronous job.
      required:
        - job_id
        - status
        - created_at
      properties:
        job_id:
          type: string
          description: Identifier assigned to the asynchronous job.
        status:
          $ref: '#/components/schemas/JobStatus'
        created_at:
          type: string
          format: date-time
          description: Timestamp when the job was accepted.
        updated_at:
          type: string
          format: date-time
          description: Timestamp of the last status update, if available.
        result:
          type: object
          description: Structured payload when the job is completed.
          additionalProperties: true
        error:
          type: string
          description: Error message describing why the job failed, if applicable.
    JobStatus:
      type: string
      description: Lifecycle status for an asynchronous job.
      enum:
        - pending
        - processing
        - completed
        - failed
        - canceled
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Error code (e.g., FILE_001, AUTH_002)
            message:
              type: string
              description: Human-readable error message
            details:
              type: object
              description: Additional error context
  responses:
    Unauthorized:
      description: Unauthorized - Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: >-
        Forbidden - Authenticated principal does not have access to this
        resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: API key for authentication

````