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

# Process API

The `/process` API is a one-call ingestion pipeline. You send files and an optional list of actions in one multipart request. CloudRaker parses each file and runs the actions. It holds the results for a TTL you choose, then purges everything. You get a processing id to poll. You can also receive [signed webhooks](/paperwork/developers/webhooks) as each event happens.

This is the fastest way to turn documents into structured data from your backend. You do not set up spaces or manage files.

For new integrations, use the JSON capability endpoints — [extract](/paperwork/capabilities/extract) and [parse](/paperwork/capabilities/parse). They take a file URL or id instead of multipart. They return the result in the same call and cite every extracted field. `/process` stays supported and unchanged for the integrations already on it.

Authorization is org-level. The caller must be an **org API key** or an **org admin**.

## The flow

#### Start a pipeline

`POST /process` with your files and options.

#### Poll for status and results

`GET /process/{id}` until `status` is `done` (or `failed` / `expired`).

#### (Optional) purge early

`DELETE /process/{id}` cancels in-flight work and deletes everything now. Or let it expire.

## Start a pipeline

`POST /process` takes `multipart/form-data`: one part named **`options`** (JSON) plus one part per file you declare.

### The `options` part

```jsonc
{
  "files": [
    { "field": "invoice", "processingKind": "doc-auto" }
  ],
  "actions": ["<installedActionId or slug>"],   // optional; default []
  "callbackUrl": "https://example.com/webhooks/rakerone",  // optional
  "durationSeconds": 86400              // optional TTL; default 86400 (24h), max 604800 (7d)
}
```

* **`files[]`** — one entry per file. `field` must match the name of a multipart part that carries the file's bytes. `processingKind` is optional.
* **`actions[]`** — the installed actions to run against the ingested files. Each entry is an installed action's **id or its per-organization slug**. The two are interchangeable everywhere the API takes an installed action. Optional; defaults to none.
* **`callbackUrl`** — a receiver for [signed webhook](/paperwork/developers/webhooks) events. Optional.
* **`durationSeconds`** — the time results live before auto-purge. Default 24h, max 7 days.

`processingKind` is one of:

| Value                          | For                                       |
| ------------------------------ | ----------------------------------------- |
| `doc-simple`                   | Text-native documents, fastest path       |
| `doc-ocr`                      | Scanned or image documents that need OCR  |
| `doc-auto`                     | CloudRaker chooses between simple and OCR |
| `audio-transcribe`             | Audio → transcript                        |
| `audio-transcribe-and-diarize` | Audio → transcript with speaker labels    |

### Request

```bash
curl -X POST https://api.cloudraker.com/process \
  -H "Authorization: Bearer $RAKERONE_API_KEY" \
  -F 'options={"files":[{"field":"invoice","processingKind":"doc-auto"}]};type=application/json' \
  -F 'invoice=@./invoice.pdf'
```

### Response — `201`

```json
{
  "processingId": "…",
  "expiresAt": "2026-07-21T00:00:00.000Z",
  "statusUrl": "/process/<processingId>"
}
```

Pipeline order: CloudRaker ingests and parses each file. It dispatches the declared actions and collects the results. It emits webhooks, holds everything until the TTL, then purges it.

The platform caps the request body at approximately 100 MB. For large files, register them into a space with the [presigned upload flow](/paperwork/developers/files-and-uploads) instead. That flow streams directly to object storage.

Possible errors: `400` (malformed multipart or manifest), `403` (caller is not an org admin and not an org API key).

## Poll for status and results

### Request

GET [https://api.cloudraker.com/process/\{id}](https://api.cloudraker.com/process/\{id})

```curl
curl https://api.cloudraker.com/process/id \
     -H "Authorization: Bearer <token>"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.process.getProcess({
        id: "id",
    });
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.process.get_process(
    id="id",
)

```

```go
package main

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

func main() {

	url := "https://api.cloudraker.com/process/id"

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

	req.Header.Add("Authorization", "Bearer <token>")

	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://api.cloudraker.com/process/id")

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

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

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.get("https://api.cloudraker.com/process/id")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.cloudraker.com/process/id', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/process/id");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/process/id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
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()
```

### Query parameters

* **`include`** — comma-separated list of `content`, `results`, `evidence`. The API inlines these heavier payloads only when you request them.
* **`format`** — `json` (default) or `markdown`.

### `ProcessStatus` response

```jsonc
{
  "processingId": "…",
  "status": "preprocessing | running | done | failed | expired",
  "expiresAt": "…",
  "files": [
    { "fileId": "…", "fileName": "invoice.pdf", "processingKind": null,
      "status": "…", "error": null, "content": null }
  ],
  "actions": [
    { "runId": null, "installedActionId": "…", "status": "…", "error": null, "result": null }
  ]
}
```

`content` (per file), `results` (per action), and `evidence` appear only when you request them with `include`.

Audio files have no markdown byproduct. `include=content&format=markdown` returns `null` for audio content. Use `format=json`.

### Action results

`actions[].result` arrives with `include=results`, once that action's `status` is `done`. Its shape depends on what the action produces.

**Actions that extract data** (extract) return the grounded result. `docs[]` holds one entry per source file, `data` holds the extracted fields, and `evidence` cites them. Citations are heavy, so they appear only with `include=results,evidence`.

```jsonc
{
  "version": 1,
  "unit": "per_document",
  "fieldKeys": ["total"],
  "docs": [
    { "id": "file_…", "name": "invoice.pdf", "status": "done",
      "data": { "total": "1240.00" },
      "evidence": { "total": [{ "fileId": "file_…", "page": 2, "text": "1,240.00" }] } }
  ]
}
```

**Actions that produce a file** (redact, fill, sign, generate, split) return `output` and `files`. `output` is the action's own report. `files` resolves the ids in `output.documentIds` to download links valid about one hour — fetch them before they expire, or poll again for fresh ones.

```jsonc
{
  "output": { "documentIds": ["file_…"], "summary": { "PERSON": 3 }, "skipped": 0 },
  "files": [
    { "id": "file_…", "name": "consult-redacted.pdf", "url": "https://cdn.cloudraker.com/…" }
  ]
}
```

Actions that return neither — classify, connector-call — carry `output` alone, with no `files`.

### When there is no result

| Field            | Meaning                                                                                                   |
| ---------------- | --------------------------------------------------------------------------------------------------------- |
| `result` present | The action produced this result.                                                                          |
| `result: null`   | The action produced no result. A fact about the run, not a failure.                                       |
| `resultError`    | The result could **not be read** — the platform, not your call. Poll again; the run itself is unaffected. |

`result: null` and `resultError` are distinct. Do not treat a missing result as an error, and do not treat a read failure as an empty result.

### Poll responses

| Status | Body                    | Meaning                                                       |
| ------ | ----------------------- | ------------------------------------------------------------- |
| `200`  | `ProcessStatus`         | Found. Poll until `status` is `done`, `failed`, or `expired`. |
| `404`  | `{"error":"not_found"}` | Unknown id, or hard-wiped after the grace window              |
| `410`  | `{"error":"expired"}`   | Purged, still inside the post-TTL grace window                |

## Purge early

### Request

DELETE [https://api.cloudraker.com/process/\{id}](https://api.cloudraker.com/process/\{id})

```curl
curl -X DELETE https://api.cloudraker.com/process/id \
     -H "Authorization: Bearer <token>"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.process.purgeProcess({
        id: "id",
    });
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.process.purge_process(
    id="id",
)

```

```go
package main

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

func main() {

	url := "https://api.cloudraker.com/process/id"

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

	req.Header.Add("Authorization", "Bearer <token>")

	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://api.cloudraker.com/process/id")

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

request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'

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.delete("https://api.cloudraker.com/process/id")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', 'https://api.cloudraker.com/process/id', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/process/id");
var request = new RestRequest(Method.DELETE);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/process/id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "DELETE"
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()
```

Cancels in-flight work immediately. Deletes files, runs, and stored results. Returns `204`.

## TTL and lifecycle

A pipeline lives until `expiresAt` (`durationSeconds` from creation; default 24h, max 7 days). At expiry, CloudRaker cancels runs, purges outputs, and deletes the files. It emits a `processing.expired` webhook. The id then answers **`410`** for a short grace window (\~24h), then **`404`** after the hard wipe. Fetch everything you need before `expiresAt`, or set a longer TTL at creation.

## Where to go next

#### [Webhooks](/developers/webhooks)

Verify the signed events `/process` emits, instead of polling.

#### [Files and uploads](/developers/files-and-uploads)

The space-scoped presigned upload flow for large files and persistent storage.