> For the complete documentation index, see [llms.txt](https://docs.pletor.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pletor.ai/documentation/api-reference/introduction.md).

# Introduction

Welcome to the **Pletor API** documentation.

Integrate Pletor's AI capabilities directly into your apps and workflows — flow discovery, on-brand asset generation, and real-time execution tracking.

#### Key Features

* **Flow Discovery** — Browse a catalog of flows with detailed descriptions, required inputs, and expected outputs.
* **Apps** — Curated, runnable apps built on top of flows, with every input, setting and output named by slug and described as JSON Schema.
* **Flexible Execution** — Run a flow with `POST /v1/runs` or an app with `POST /v2/apps/{app}/runs`, providing dynamic inputs including uploaded files.
* **Asset Management** — Centralized handling for your files: upload, retrieve, and download assets, and organize them with tags, bookmarks, archiving, and deletion.
* **Real-Time Tracking** — Monitor executions with live updates and retrieve results as soon as they're ready.
* **Secure API Access** — All endpoints require an API key or a Bearer token.

#### Versioning

Every path starts with its version, under one base URL: `https://api.pletor.ai/api/public`.

| Resource               | Version | Base path    |
| ---------------------- | ------- | ------------ |
| Flows                  | v1      | `/v1/flows`  |
| Runs (flow executions) | v1      | `/v1/runs`   |
| Assets                 | v1      | `/v1/assets` |
| Apps and app runs      | v2      | `/v2/apps`   |

Each resource lives in exactly one current version, so an integration mixes them freely: upload with `/v1/assets`, then run an app with `/v2/apps/{app}/runs`. Authentication, rate limits, pagination, errors and `external_id` work the same in every version.

A version is introduced only where a resource changes shape. Deprecated endpoints keep working and are flagged in this reference:

* `/v1/apps` and `/v1/app-runs` are superseded by `/v2/apps`. They keep working unchanged.
* `/v1/agents` has been renamed to `/v1/flows`. The old paths permanently redirect (`308`) and are **removed on 31 December 2026**. `POST /v1/runs` now takes `flow_id`; it still accepts `agent_id`, and responses carry both until the same date.

#### The API runs your saved version

Editing a flow in Studio autosaves it as a private draft. The API never reads drafts — it reads the flow's last **saved version**, the one you create with the save button in the Studio toolbar ("Save in version history").

* A flow that has never been saved cannot be used: `GET /v1/flows/{flow_id}` answers `422` and `POST /v1/runs` answers `400 invalid_input`. It still appears in `GET /v1/flows/`, with an empty `inputs` array — read that as "not saved yet", not "takes no input".
* Edits made since the last save do not reach the API. Save again to roll them out to your integration.
* `GET /v1/flows/{flow_id}` describes the saved version, so its `inputs` are the ones `POST /v1/runs` expects.

#### Brand context is part of the flow

The brand (Brain) a flow generates for is set on the flow in Studio, not per request. Brand nodes are never listed in `inputs`, and no run endpoint accepts a brand or Brain to swap in. Content a flow links from a Brain is read from that Brain as it stands when the run starts. To generate for another brand, run a flow built for that brand.

#### Authentication

Every request needs one of:

* an API key in the `X-Api-Key` header, or
* a Bearer JWT in the `Authorization: Bearer <token>` header. When both are sent, the Bearer token is used.

Requests act in one organization:

* An API key bound to an organization always acts in it. `X-Organization-Id` is optional; sending a different organization answers `403 forbidden`.
* An unbound API key or a Bearer token acts in the organization named by `X-Organization-Id`, or in the user's current (else first) organization when the header is absent. A value that is not a UUID answers `400 invalid_input`; an organization the user doesn't belong to answers `403 forbidden`.

A missing or unknown API key, or an invalid or expired token, answers `401 authentication_required` with a `WWW-Authenticate` header.

#### Rate Limits

Requests authenticated with `X-Api-Key` are rate limited per API key, in fixed one-minute windows:

* **Default**: 2000 requests/minute
* **Starting a run** — `POST /v1/runs`, and `POST /v2/apps/{app}/runs` or `POST /v1/app-runs` (one shared budget), including their review submissions: 600 requests/minute each
* **POST /v1/assets/upload**: 600 requests/minute

Each response to an API-key request includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (the Unix time, in seconds, at which the window resets). Over the limit, the API answers `429 rate_limit_exceeded` with a `Retry-After` header and `details.retry_after`, both in seconds. Bearer-token requests are not counted and carry no rate-limit headers.

#### Pagination

List endpoints return `{"data": [...], "has_more": ..., "next_cursor": ...}`. Pass `limit` (1–100, default 20) and, for the next page, the previous response's `next_cursor` as `cursor`, unchanged. `has_more` is `false` on the last page.

#### Errors

Every response carries an `X-Request-Id` header. Errors use one envelope, with the same ID in `details.request_id`:

```json
{"error": {"code": "not_found", "message": "Flow not found", "status": 404, "details": {"request_id": "550e8400-e29b-41d4-a716-446655440000"}}}
```

| `code`                    | HTTP status | Meaning                                                                                                                                                                                                              |
| ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_input`           | 400         | The request is well-formed but can't be applied, e.g. run inputs that don't match the flow. Other 4xx statuses without a code of their own (such as `410` for a deleted asset) also use it, with their own `status`. |
| `authentication_required` | 401         | Missing or invalid API key or token.                                                                                                                                                                                 |
| `forbidden`               | 403         | The organization selected for the request is not allowed. A resource you can't access answers `404`, not `403`.                                                                                                      |
| `not_found`               | 404         | The resource doesn't exist or isn't accessible to you.                                                                                                                                                               |
| `conflict`                | 409         | The request clashes with current state, e.g. a reused `external_id` or a review that is no longer pending.                                                                                                           |
| `validation_error`        | 422         | A parameter or body field failed validation (each problem is listed in `details.errors` as `location: message`), or the resource can't be used yet, e.g. a flow that was never saved.                                |
| `rate_limit_exceeded`     | 429         | Too many requests; see Rate Limits.                                                                                                                                                                                  |
| `internal_error`          | 500         | Unexpected server error. Retry, and quote the `request_id` if it persists.                                                                                                                                           |
| `run_failed`              | 502         | An upstream AI provider failed or its content moderation blocked the request.                                                                                                                                        |
| `service_unavailable`     | 503         | A dependency is temporarily unavailable. Retry; a `Retry-After` header is sent when a wait is known.                                                                                                                 |
| `timeout`                 | 504         | The request timed out.                                                                                                                                                                                               |

A path or method the API doesn't define answers `404` or `405` with `{"detail": "..."}` instead of this envelope.

`401`, `403`, `429` and the `5xx` codes can come from any endpoint; each endpoint's own error list covers the codes specific to it.

#### Idempotent requests

Network calls fail halfway, and a blind retry can start a second run (charging you twice) or upload the same file again. To retry safely, attach an `external_id` to any `POST` and reuse the same value on the retry.

* The first request with a key creates the object and returns it, with `external_id` echoed back.
* Any later request that reuses the key returns `409 conflict` instead of creating a duplicate. Fetch the original with `GET /v1/runs?external_id=...` (the same filter works on `/v2/apps/{app}/runs`, `/v1/app-runs` and `/v1/assets`).
* `GET` and `DELETE` don't take a key. Repeating them changes nothing more, but the repeat can answer differently: cancelling a run or app run again answers `404`, and deleting an asset again answers `410`.

A key is **unique per organization and permanent**. Once it has created a run or an upload it stays claimed, even if the run later fails or is canceled, so a genuine re-run needs a new key. Runs and uploads are tracked separately, so the same value can label one run and one upload but not two of either.

Generate your own key. A v4 UUID is a good default; keys can be up to 255 characters, and a longer one is rejected with `422`. Keep sensitive data out of it, since it appears in responses. A request that fails validation before the object is created (bad inputs, an oversized key) claims nothing, so you can fix it and retry with the same key.

#### Quickstart

1. **Find a flow or an app** — `GET /v1/flows` or `GET /v2/apps`.
2. **Inspect inputs** — `GET /v1/flows/{flow_id}` lists a flow's inputs and outputs; `GET /v2/apps/{app}` returns an app's `input_schema` and `output_schema` as JSON Schema.
3. **Upload assets** (if needed) — `POST /v1/assets/upload`.
4. **Start a run** — `POST /v1/runs` with a `flow_id`, or `POST /v2/apps/{app}/runs`, plus inputs.
5. **Poll status** — `GET /v1/runs/{run_id}` or `GET /v2/apps/{app}/runs/{run_id}` until `status` leaves `in_progress` for `completed`, `failed` or `canceled`.
6. **Handle human review** (if the flow or app has `has_human_review`) — when `awaiting_human_review` is `true`, the run is paused: resolve each entry in `pending_reviews` via `POST /v1/runs/{run_id}/reviews/{review_id}` or `POST /v2/apps/{app}/runs/{run_id}/reviews/{review_id}` to resume execution.
7. **Retrieve outputs** — a completed flow run lists generated `asset_id`s and text in `results`; an app run keys them by slug in `outputs`.
8. **Download assets** — `GET /v1/assets/{asset_id}/download`.
