> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.emtelligent.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.emtelligent.com/_mcp/server.

# Submit a document

POST https://ocr.emtelligent.com/jobs
Content-Type: application/octet-stream

Submit one document as the raw request body and start a job.

Poll ``status_url`` until ``state`` is ``done`` or ``failed``, then fetch
``result_url``.

``X-Filename`` sets the job's display name. It does not affect
processing. ``X-Output-Type`` sets the text format of tables in the
result. ``X-Header-Footer-Marker`` sets whether the result wraps page
header and footer text in tags. ``Idempotency-Key`` de-duplicates
submissions in your organization. When a job with the same key exists,
the response is 200 with that job's status, and the server does not read
the body. The key becomes free again when its job is deleted or expires.

Reference: https://docs.emtelligent.com/ocr/api/submit-job

## Authentication

- `Authorization` header (bearer token, required) — An emtelligent API key, sent as `Authorization: Bearer <key>`.

## Request

### Headers

- `x-filename` (string, optional, nullable) — Optional display name for the job, percent-encoded as UTF-8. Metadata only; does not affect processing.
- `idempotency-key` (string, optional, nullable) — Optional de-duplication key, scoped to your organization. A repeat submission with the same key answers 200 with the existing job's status and skips the upload. A repeat with a different output type answers 422.
- `x-output-type` (enum, optional, nullable) — Optional text format for tables in the result's `text` fields: `markdown`, `plaintext` or `alignedtext`. Text that is not in a table does not change. When omitted, the server's default type applies. The job status shows the type of the job.
  - Allowed values: `markdown`, `plaintext`, `alignedtext`
- `x-header-footer-marker` (boolean, optional, nullable) — Optional. When `true`, the result wraps the text of each page header block in `<HEADER></HEADER>` tags and the text of each page footer block in `<FOOTER></FOOTER>` tags. When `false`, the text has no tags. When omitted, the server's default applies.

### Body (application/octet-stream)

This endpoint expects binary data of type application/octet-stream.

- Binary request body.

## Response

### 200

Idempotent replay: a job with this `Idempotency-Key` already exists in your organization. Its current status is returned and no job is created.

- `job_id` (string, required)
- `filename` (string, required)
- `output_type` (enum, required)
  - Allowed values: `markdown`, `plaintext`, `alignedtext`
- `state` (enum, required)
  - Allowed values: `queued`, `rendering`, `ocr`, `done`, `failed`
- `created_at` (string, required)
- `updated_at` (string, required)
- `render` (RenderStage, required)
- `ocr` (OCRStage, required)
- `mime_type` (enum, optional, nullable)
  - Allowed values: `application/pdf`, `image/jpeg`, `image/png`, `image/tiff`
- `finished_at` (string, optional, nullable)
- `results_expire_at` (string, optional, nullable)
- `results_purged_at` (string, optional, nullable)
- `purge_reason` (enum, optional, nullable) — Why the job's content was deleted.
  - Allowed values: `expired`, `deleted`
- `purged_by` (string, optional, nullable)
- `error` (string, optional, nullable)
- `page_timings` (PageTimingReport, optional, nullable) — Per-page timings for the render and OCR stages.
- `ocr_service` (map from string to integer, optional, nullable)
- `page_budget` (map from string to integer, optional, nullable)
- `render_pool` (map from string to integer, optional, nullable)
- `result_available` (boolean, optional, default: false)

### 201

The upload was accepted and the job was created.

- `job_id` (string, required)
- `filename` (string, required)
- `mime_type` (enum, required)
  - Allowed values: `application/pdf`, `image/jpeg`, `image/png`, `image/tiff`
- `output_type` (enum, required)
  - Allowed values: `markdown`, `plaintext`, `alignedtext`
- `state` (enum, required)
  - Allowed values: `queued`, `rendering`, `ocr`, `done`, `failed`
- `bytes_received` (integer, required)
- `status_url` (string, required)
- `result_url` (string, required)

## Errors

### 400 Bad Request Error

The request body is empty.

- `detail` (string, required)

### 401 Unauthorized Error

The bearer token is missing, unknown or expired, or the key does not have the `ocr:process` scope.

- `detail` (string, required)

### 402 Payment Required Error

Your organization is suspended or out of credit.

- `detail` (string, required)

### 409 Conflict Error

A concurrent submission with the same `Idempotency-Key` failed during its upload (retry the request), or a delete request removed the job during the upload.

- `detail` (string, required)

### 413 Content Too Large Error

The body is larger than the upload limit.

- `detail` (string, required)

### 415 Unsupported Media Type Error

The file type was not recognized. The supported types are PDF, PNG, JPEG and TIFF.

- `detail` (string, required)

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

### 503 Service Unavailable Error

The service cannot verify the key at this time. Retry later.

- `detail` (string, required)

## Types

### RenderStage

- `total_pages` (integer, optional, nullable)
- `rendered` (integer, optional, default: 0)
- `failed` (integer, optional, default: 0)
- `loaded_from_log` (integer, optional, default: 0)
- `in_flight_pages` (integer, optional, default: 0)

### OCRStage

- `submitted` (integer, optional, default: 0)
- `completed` (integer, optional, default: 0)
- `failed` (integer, optional, default: 0)
- `in_flight` (integer, optional, default: 0)

### PageTimingReport

Per-page timings for the render and OCR stages.

- `render` (PageTimingStats, optional) — Distribution of per-page durations for one stage, in milliseconds. ``count`` is the number of pages that finished the stage. The other fields are null until at least one page finishes.
- `ocr` (PageTimingStats, optional) — Distribution of per-page durations for one stage, in milliseconds. ``count`` is the number of pages that finished the stage. The other fields are null until at least one page finishes.
- `span_ms` (double, optional, nullable)
- `pages` (list of PageTiming, optional)

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `input` (any, optional)
- `ctx` (ValidationErrorCtx, optional)

### PageTimingStats

Distribution of per-page durations for one stage, in milliseconds. ``count`` is the number of pages that finished the stage. The other fields are null until at least one page finishes.

- `count` (integer, optional, default: 0)
- `total_ms` (double, optional, default: 0)
- `min_ms` (double, optional, nullable)
- `max_ms` (double, optional, nullable)
- `mean_ms` (double, optional, nullable)
- `p50_ms` (double, optional, nullable)
- `p95_ms` (double, optional, nullable)
- `slowest_page` (integer, optional, nullable)

### PageTiming

One page's timeline. The ``*_start_ms`` and ``*_end_ms`` values are offsets in milliseconds from the start of the earliest page work in the job. The other ``*_ms`` values are durations. A field is null for a stage that the page has not finished.

- `page` (integer, required)
- `render_start_ms` (double, optional, nullable)
- `render_end_ms` (double, optional, nullable)
- `render_ms` (double, optional, nullable)
- `ocr_start_ms` (double, optional, nullable)
- `ocr_end_ms` (double, optional, nullable)
- `ocr_ms` (double, optional, nullable)

### ValidationErrorLocItems

- string
- integer

### ValidationErrorCtx

## Examples

### Example 1

**Response**

```json
{
  "job_id": "string",
  "filename": "string",
  "output_type": "markdown",
  "state": "queued",
  "created_at": "string",
  "updated_at": "string",
  "render": {
    "total_pages": 1,
    "rendered": 0,
    "failed": 0,
    "loaded_from_log": 0,
    "in_flight_pages": 0
  },
  "ocr": {
    "submitted": 0,
    "completed": 0,
    "failed": 0,
    "in_flight": 0
  },
  "mime_type": "application/pdf",
  "finished_at": "string",
  "results_expire_at": "string",
  "results_purged_at": "string",
  "purge_reason": "expired",
  "purged_by": "string",
  "error": "string",
  "page_timings": {
    "render": {
      "count": 0,
      "total_ms": 0,
      "min_ms": 1.1,
      "max_ms": 1.1,
      "mean_ms": 1.1,
      "p50_ms": 1.1,
      "p95_ms": 1.1,
      "slowest_page": 1
    },
    "ocr": {
      "count": 0,
      "total_ms": 0,
      "min_ms": 1.1,
      "max_ms": 1.1,
      "mean_ms": 1.1,
      "p50_ms": 1.1,
      "p95_ms": 1.1,
      "slowest_page": 1
    },
    "span_ms": 1.1,
    "pages": [
      {
        "page": 1,
        "render_start_ms": 1.1,
        "render_end_ms": 1.1,
        "render_ms": 1.1,
        "ocr_start_ms": 1.1,
        "ocr_end_ms": 1.1,
        "ocr_ms": 1.1
      }
    ]
  },
  "ocr_service": {},
  "page_budget": {},
  "render_pool": {},
  "result_available": false
}
```

**SDK Code**

```python
import requests

url = "https://ocr.emtelligent.com/jobs"

headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/octet-stream"
}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://ocr.emtelligent.com/jobs';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/octet-stream'}
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://ocr.emtelligent.com/jobs"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/octet-stream")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://ocr.emtelligent.com/jobs")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/octet-stream'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://ocr.emtelligent.com/jobs")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/octet-stream")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://ocr.emtelligent.com/jobs', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/octet-stream',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://ocr.emtelligent.com/jobs");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/octet-stream");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/octet-stream"
]

let request = NSMutableURLRequest(url: NSURL(string: "https://ocr.emtelligent.com/jobs")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Example 2

**Response**

```json
{
  "job_id": "string",
  "filename": "string",
  "output_type": "markdown",
  "state": "queued",
  "mime_type": "application/pdf",
  "bytes_received": 1,
  "result_url": "string",
  "status_url": "string"
}
```

**SDK Code**

```python
import requests

url = "https://ocr.emtelligent.com/jobs"

headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/octet-stream"
}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://ocr.emtelligent.com/jobs';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/octet-stream'}
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://ocr.emtelligent.com/jobs"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/octet-stream")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://ocr.emtelligent.com/jobs")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/octet-stream'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://ocr.emtelligent.com/jobs")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/octet-stream")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://ocr.emtelligent.com/jobs', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/octet-stream',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://ocr.emtelligent.com/jobs");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/octet-stream");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/octet-stream"
]

let request = NSMutableURLRequest(url: NSURL(string: "https://ocr.emtelligent.com/jobs")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```