# Synclo API reference

> Generate presentations and documents from your own code. Send a topic or source text; Synclo writes and designs the presentation, saves it in your workspace and returns its links. Web version: https://synclo.app/developers. OpenAPI 3.1: https://synclo.app/api/v1/openapi.json.

## Quick reference

- Base URL: `https://synclo.app/api/v1`. Requests and responses are JSON over HTTPS.
- Authenticate with an API key in the `Authorization` header: `Bearer sy_…`. Workspace admins create keys in Settings › API.
- Generation is asynchronous: `POST /generations` returns `202` with a generation `id`; poll `GET /generations/{id}` every 5 seconds until `status` is `completed` or `failed`.
- A generation costs 10 credits per slide, the same as in the app. Failed generations aren't charged.
- Send an `Idempotency-Key` with `POST /generations` so a retried request never creates or charges twice.
- Every response carries a `Synclo-Request-Id`; include it when you contact support.

## Authentication

Send an API key as a bearer token: `Authorization: Bearer sy_…`. Workspace admins create and revoke keys in Settings › API. A key belongs to its workspace: generated presentations are saved there, and credits come from its balance. API access is included with Pro, Max and Business; other plans return `402` `plan_required`. Keep keys on your server, never in client-side code.

## Quick start

Create a generation, then poll it every 5 seconds until `status` is `completed` or `failed`. Set `SYNCLO_API_KEY` first.

```bash
ID=$(curl -s https://synclo.app/api/v1/generations \
  -H "Authorization: Bearer $SYNCLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"A pitch for a neighborhood bike repair shop","slides":8}' | jq -r .id)

while true; do
  GEN=$(curl -s https://synclo.app/api/v1/generations/$ID \
    -H "Authorization: Bearer $SYNCLO_API_KEY")
  STATUS=$(echo "$GEN" | jq -r .status)
  if [ "$STATUS" = completed ] || [ "$STATUS" = failed ]; then break; fi
  sleep 5
done

echo "$GEN" | jq '.document // .error'
```

```python
import os, time, requests

API = "https://synclo.app/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SYNCLO_API_KEY']}"}

res = requests.post(f"{API}/generations", headers=HEADERS, json={
    "input": "A pitch for a neighborhood bike repair shop",
    "slides": 8,
})
res.raise_for_status()
generation = res.json()

while generation["status"] in ("queued", "running"):
    time.sleep(5)
    res = requests.get(f"{API}/generations/{generation['id']}", headers=HEADERS)
    res.raise_for_status()
    generation = res.json()

if generation["status"] == "completed":
    print(generation["document"]["edit_url"])
else:
    print(generation["error"]["message"])
```

```javascript
const API = "https://synclo.app/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.SYNCLO_API_KEY}`,
  "Content-Type": "application/json",
};

let res = await fetch(`${API}/generations`, {
  method: "POST",
  headers,
  body: JSON.stringify({ input: "A pitch for a neighborhood bike repair shop", slides: 8 }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
let generation = await res.json();

while (generation.status === "queued" || generation.status === "running") {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  res = await fetch(`${API}/generations/${generation.id}`, { headers });
  if (!res.ok) throw new Error((await res.json()).error.message);
  generation = await res.json();
}

console.log(generation.status === "completed" ? generation.document.edit_url : generation.error.message);
```

## Credits

A generation costs 10 credits per slide, the same as in the app; images generated with `auto` are included. The estimate (10 credits per requested slide) is checked against the balance when the generation is created: a lower balance returns `402` `insufficient_credits`. Failed generations aren't charged.

## POST /generations

Creates a presentation or document. Returns `202 Accepted` with the generation (the same shape as `GET /generations/{id}`) and a `Location` header. Unknown parameters are rejected with `400`.

Headers:

- `Idempotency-Key` (header): Optional, 1 to 255 characters. A retry with the same key and body within 24 hours returns the original generation with `Idempotent-Replayed: true` and isn't charged again. See [Idempotency](https://synclo.app/developers#idempotency).

Body:

- `input` (string, required): The content to generate from. With `generate`, a topic or brief; with `condense` or `preserve`, the source text, at least 20 words. Up to 400,000 characters; longer input returns `413` `too_large`.
- `mode` (string): How `input` is used. `generate` writes new content from a topic or brief. `condense` summarizes the source text into slides and keeps its facts. `preserve` keeps the source text as written. Default: `generate`.
- `format` (string): `slides` for a presentation, `document` for a document of sections. Default: `slides`.
- `slides` (integer): The number of slides, the opening slide included (sections, for a document), from 1 to 20. With `condense` and `preserve`, the maximum: the source text sets the number, up to this value. Every slide is charged 10 credits. Default: `8`.
- `theme` (string): The `id` of a theme from [GET /themes](https://synclo.app/developers#themes). If omitted, Synclo designs a look for the presentation. Default: A look designed for the presentation.
- `template` (string): The `id` of a template from [GET /templates](https://synclo.app/developers#templates). The presentation follows the template's structure, and its look unless `theme` or `brand` is set. Default: None.
- `aspect` (string): The slide aspect ratio. Applies to `slides` only. One of: `16:9`, `4:3`. Default: `16:9`.
- `page_size` (string): `fluid` pages size to their content; `letter` and `a4` are fixed print sizes. Applies to `document` only. Default: `fluid`.
- `text` (object): Text options.
- `text.amount` (string): How much text each slide carries. One of: `minimal`, `concise`, `detailed`, `extensive`. Default: Chosen per slide.
- `text.tone` (string): The writing style, up to 100 characters, such as `warm` or `formal`.
- `text.audience` (string): The intended audience, up to 200 characters, such as `local investors`.
- `text.language` (string): The output language: a BCP 47 tag such as `fr` or `pt-BR`, or a language name such as `Japanese`. Default: The language of `input`.
- `images` (object): Image options.
- `images.source` (string): `ai` generates images. `placeholders` adds an empty image slot to each slide, to fill in the editor. `none` adds no images. Default: `ai`.
- `images.model` (string): `auto`, or the `id` of a model from [Image models](https://synclo.app/developers#image-models). With `auto`, Synclo chooses the model and images are included in the slide price; other models may add credits per image. One of: `auto`, `seedream-5-flash`, `flux-2-fast`, `nano-banana-2-lite`, `nano-banana-2.1`, `gpt-image-2.5-flare`, `flux-3`, `seedream-5-pro`, `recraft-v4.1`, `nano-banana-2.1-hd`. Default: `auto`.
- `images.style` (string): `theme` uses the theme's image style. Otherwise, the `id` of a style from [Image styles](https://synclo.app/developers#image-styles), or a description of up to 500 characters, such as `pencil sketches on cream paper`. Default: `theme`.
- `instructions` (string): Additional instructions for the content and design, up to 2,000 characters, such as `Close on the ask.`
- `brand` (boolean): If `true`, applies the workspace's brand from Settings › Brand: logo, fonts and colors. Returns `400` if the workspace has no brand. Default: `false`.
- `share` (string): `link` turns on the public link, returned as `document.share_url`. One of: `private`, `link`. Default: `private`.
- `export` (string): Generates a file with the presentation: `pdf`, `pptx` (PowerPoint), or `png` (a ZIP with one PNG per slide; a single PNG for a one-slide presentation). Default: None.

```bash
curl -X POST https://synclo.app/api/v1/generations \
  -H "Authorization: Bearer $SYNCLO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c9e1a-2b7d-4c55-9d0e-1a2b3c4d5e6f" \
  -d '{
    "input": "A pitch for a neighborhood bike repair shop",
    "mode": "generate",
    "format": "slides",
    "slides": 8,
    "theme": "cobalt",
    "aspect": "16:9",
    "text": {
      "amount": "concise",
      "tone": "warm",
      "audience": "local investors",
      "language": "en"
    },
    "images": {
      "source": "ai",
      "model": "auto",
      "style": "theme"
    },
    "instructions": "Close on the ask.",
    "brand": false,
    "share": "private",
    "export": "pdf"
  }'
```

Response `202`:

```json
{
  "id": "gen_3f2a9c0e7b1d4e8a9c6f2b5d8e1a4c7b",
  "object": "generation",
  "status": "queued",
  "created_at": "2026-10-12T14:03:11Z",
  "started_at": null,
  "completed_at": null,
  "document": null,
  "export": null,
  "credits": {
    "estimated": 80,
    "used": null,
    "remaining": null
  },
  "warnings": [],
  "error": null
}
```

## GET /generations/{id}

Returns a generation. Poll every 5 seconds until `status` is `completed` or `failed`.

- `id` (string): Unique identifier: `gen_` followed by 32 hexadecimal characters.
- `object` (string): Always `generation`.
- `status` (string): `queued`, `running`, `completed` or `failed`.
- `created_at` (string): When the generation was created (ISO 8601, UTC).
- `started_at` (string or null): When processing started; `null` while queued.
- `completed_at` (string or null): When the generation completed or failed; `null` until then.
- `document` (object or null): The generated presentation once `status` is `completed`; otherwise `null`.
- `document.id` (string): The document ID, for [GET /documents/{id}](https://synclo.app/developers#document).
- `document.title` (string): The title.
- `document.slides` (integer): The number of slides (or sections), including the opening slide.
- `document.edit_url` (string): Opens the presentation in the Synclo editor. Requires a workspace member's sign-in.
- `document.share_url` (string or null): The public link, or `null` if the link is off.
- `export` (object or null): The file requested with `export`, or `null`. `url` requires no authentication and expires after 7 days; treat it as a secret. If the file couldn't be generated, the presentation is still created, `url` is `null` and `error` explains why.
- `export.format` (string): The requested format. One of: `pdf`, `pptx`, `png`.
- `export.url` (string or null): The download URL.
- `export.expires_at` (string): When `url` expires.
- `export.error` (string): Present only if the file couldn't be generated.
- `credits` (object): Credit usage.
- `credits.estimated` (integer): 10 credits per requested slide, checked against the balance when the generation was created.
- `credits.used` (integer or null): Credits charged: `null` until the generation finishes, `0` if it failed.
- `credits.remaining` (integer or null): The workspace's balance after the generation, once `completed`; otherwise `null`.
- `warnings` (array of strings): Non-fatal issues: the presentation was created, but a requested option couldn't be applied (for example, the public link couldn't be turned on). Usually empty.
- `error` (object or null): `code` and `message` when `status` is `failed`; otherwise `null`. Failed generations aren't charged. See [Errors](https://synclo.app/developers#errors).

Response `200` (completed):

```json
{
  "id": "gen_3f2a9c0e7b1d4e8a9c6f2b5d8e1a4c7b",
  "object": "generation",
  "status": "completed",
  "created_at": "2026-10-12T14:03:11Z",
  "started_at": "2026-10-12T14:03:12Z",
  "completed_at": "2026-10-12T14:05:02Z",
  "document": {
    "id": "8c1e5b2a-4d3f-4a7e-9b1c-2d6e8f0a3b5c",
    "title": "Spoke & Sprocket",
    "slides": 8,
    "edit_url": "https://synclo.app/d/8c1e5b2a-4d3f-4a7e-9b1c-2d6e8f0a3b5c",
    "share_url": "https://acme.synclo.site/r/spoke-sprocket-x7Kq9TmA2pD"
  },
  "export": {
    "format": "pdf",
    "url": "https://synclo.app/files/kQ7mX2pLw9RaT3vB/spoke-and-sprocket.pdf",
    "expires_at": "2026-10-19T14:05:02Z"
  },
  "credits": {
    "estimated": 80,
    "used": 80,
    "remaining": 1920
  },
  "warnings": [],
  "error": null
}
```

## GET /themes

Returns `{ themes: [{ id, name, kind, description }] }`: Synclo's themes (`kind: prebuilt`), then the workspace's own (`kind: workspace`). Synclo's themes:

- `cobalt`: Cobalt, Confident business decks
- `midnight`: Midnight, Launches, tech and evening events
- `paprika`: Paprika, Food, events and small businesses
- `recess`: Recess, Lessons and classrooms
- `gridline`: Gridline, Data reports, reviews and research
- `charter`: Charter, Boards, finance, legal and consulting
- `billboard`: Billboard, Campaigns, launches and announcements
- `zurich`: Zurich, Design, architecture and strategy
- `fern`: Fern, Health, sustainability and nonprofits
- `bloom`: Bloom, People, wellbeing and community
- `gala`: Gala, Celebrations and invitations
- `folio`: Folio, Reports, essays and pre-reads
- `ledger`: Ledger, Law, finance and academia
- `mono`: Mono, Product specs and engineering
- `vow`: Vow, Weddings, invitations and hospitality
- `clay`: Clay, Cafés, craft and interiors
- `marigold`: Marigold, Posters, youth and community events
- `ink`: Ink, Pitch decks and keynotes
- `graphite`: Graphite, Product reviews and strategy
- `noir`: Noir, Luxury, fashion and real estate
- `forest`: Forest, Nature, wellness and retreats
- `ember`: Ember, Restaurants, nights out and sports

## GET /templates

Returns `{ templates: [{ id, name, description, good_for }] }`:

- `waypoint`: Lesson. A lesson built as one adventure in one illustrated world: a hook, the skills one stop at a time, a quiz, a partner activity and an exit ticket.
- `threshold`: Portfolio. Selected work told the same way each time: before and after at one seam, the details, what changed and the client's words.
- `plumbline`: Proposal. A client proposal that shows the work as it is and as it will be, the options side by side, the schedule and every line of the price.
- `transit`: Research report. Survey findings where every chart makes one point and says who was asked, how many and what the question was.
- `block-party`: Event invitation. An invitation that runs the whole day: when and where, the programme, a map, what to bring, how to help and how to reply.
- `debut`: Product launch. A launch told through the product itself: its screens built as real cards, three customers followed through their day.
- `fanfare`: Marketing campaign. A campaign plan at poster volume: the line, the creative, where it runs, the weeks and the numbers that prove it worked.
- `monday`: Weekly update. A team's week on one strip, Monday to Friday: what got done, what is at risk and the decisions needed.
- `groundwork`: Project status. Every measure read as planned against actual, with today marked wherever time appears and the site photographed month by month.
- `caliper`: Quarterly review. A quarter read like an instrument: every figure against its target and tolerance, red only where it is out.
- `upswing`: Seed pitch. A raise told along the company's own growth curve: flat on the problem, rising on the proof, dashed into the plan.
- `proof`: Case study. A customer story told as measured change: every claim a before and an after.
- `chroma`: Product catalogue. A range where every product owns a color: its page, its photograph and its place in the index.
- `huddle`: Workshop. A hands-on session in three modules, each with its own shape and color, drawn as warm flat illustration.
- `show-and-tell`: School project. A bright class project where the subject itself appears on every slide, big enough to read from the back row.
- `runway`: Investor update. The quarter told against the runway: every figure read against how long the money lasts.
- `overcast`: Lesson. A lesson that makes a class see it: a hook, a vote, the story in pictures, a check at the end.
- `throughline`: Strategy report. The answer on the cover, then the case: every title a claim, every slide placed in the argument.
- `lineup`: Wedding weekend. Three days told as a small festival: the line-up, the set times, the stages and the wristband.
- `endpoint`: Research talk. A study presented figure by figure, each with its takeaway in plain words.
- `hush`: Client proposal. From the problem you can measure to the change you promise, priced and scheduled.
- `forecast`: Product roadmap. What ships now, what comes next and what is still a chance, with the odds on each.
- `playback`: Research readout. What customers told you, each finding with how many said it.
- `shade`: Impact report. A year of work for donors and partners, measured in what changed.
- `call-sheet`: Trip plan. Who's coming, each day's plan and call time, the bookings and the budget.
- `wayfinding`: Onboarding. A new hire's first month: the people, the tools, the rituals and the checklist.
- `pitch-tally`: Seed pitch. The problem, the traction and the ask, for investors.
- `report-review`: Business review. A leadership team’s numbers, one point a slide.
- `lesson-water-cycle`: Lesson. A class from the hook to the exit question, with one activity.
- `proposal-harbor-pine`: Client proposal. The scope, the approach, the timeline and the price.
- `themes-talus`: Product launch. What it is, who it’s for and how it sells.
- `editor-kickoff`: Sales presentation. What customers get, with the numbers that prove it.
- `research-heat`: Conference talk. Research turned into a talk, every source kept.
- `teams-weekly`: Weekly update. What shipped, what’s next and where the team needs help.
- `present-bikes`: Year in review. A year’s story for a room: what happened, what it cost, what’s next.
- `pdf-greenline`: Board update. A quarter’s results and decisions, ready for the board.
- `export-fitout`: Project pitch. Options side by side, with the costs and a timeline.
- `invite-sam`: Party invitation. Who, when and where, and the one thing to keep quiet.
- `doc-riverside`: Grant application. The problem, the plan, the budget and who does what.

## GET /me

Returns `{ workspace: { id, name }, plan: { id, name }, credits }`: the API key's workspace, its plan and its credit balance.

## GET /documents and GET /documents/{id}

`GET /documents` returns `{ documents: [{ id, title, updated_at, created_at, share_url }] }`, most recently updated first, up to 100. `GET /documents/{id}` returns `{ id, title, theme, updated_at, created_at, share_url, document }`, where `document` holds the sections and blocks.

## MCP server

`POST https://synclo.app/mcp`. The MCP server lets Claude, ChatGPT and other MCP clients create and read presentations for a person who signs in with their Synclo account. It's the server behind the Claude and ChatGPT connector (https://synclo.app/connectors).

- Transport: Streamable HTTP. Send one JSON-RPC 2.0 message per `POST`; responses are JSON. Notifications return `202` with no body, and `GET` returns `405`.
- Protocol versions: `2025-11-25`, `2025-06-18` and `2025-03-26`.
- Authorization: an OAuth access token for the person, sent as `Authorization: Bearer syo_…` (see OAuth below).
- Plans: every plan, Free included. A connection acts in one workspace, which the person picks when they allow access.

```bash
claude mcp add --transport http synclo https://synclo.app/mcp
```

```json
{
  "mcpServers": {
    "synclo": {
      "type": "http",
      "url": "https://synclo.app/mcp"
    }
  }
}
```

### Tools

| Tool | Title | Scope | Description |
| --- | --- | --- | --- |
| `create_presentation` | Create a presentation | `presentations:write` | Creates a designed presentation in Synclo: a pitch deck, a sales or board deck, a lesson, a report, a proposal or any slides, written and designed with pictures and charts, or a document of sections. Works from a topic, a brief, an outline or source text, and saves it in the connected workspace. Returns a generation_id at once; a presentation takes three to five minutes, and in chat apps that show Synclo's card, the card shows it as it is written and when it is ready. Costs 10 credits per slide, the opening slide included. A repeat of the same request within 10 minutes returns the first generation. |
| `get_generation` | Check on a presentation | `presentations:read` | Returns a generation's status. Waits up to 40 seconds for it to finish. When completed, returns the presentation's title, slide count, editor link, public link (if on) and the credits used and remaining; when failed, the reason. In chat apps that show Synclo's card, the card under create_presentation already shows the finished presentation. |
| `export_presentation` | Export a presentation | `presentations:read` | Creates a PDF, PowerPoint (pptx) or PNG file of a presentation in the workspace and returns a download link that works for 7 days. A workspace can make up to 50 files a day through connected apps, one at a time. |
| `search_presentations` | Find presentations | `presentations:read` | Lists the workspace's presentations and documents the person can see, most recently updated first, optionally filtered by words in the title. |
| `show_presentation` | Show a presentation | `presentations:read` | Shows a presentation from the workspace in the chat, slide by slide, with links to open, present and download it. Returns its title, slide count and editor link. A presentation made with create_presentation in this chat is shown by its own card already. |
| `read_presentation` | Read a presentation | `presentations:read` | Returns a presentation's text, slide by slide: each slide's title and words. |
| `list_themes` | List themes | `presentations:read` | Lists the themes a presentation can use: Synclo's, then the workspace's own. |
| `list_templates` | List templates | `presentations:read` | Lists the templates a presentation can follow. |

### create_presentation arguments

- `input` (string, required): What to make (a topic or brief), or the source text to turn into slides. Up to 400,000 characters.
- `mode` (string): generate writes new content from a topic or a brief; condense makes slides from source text, tightening its words and keeping its facts; preserve keeps every word of the source text as written. Default generate. One of: `generate`, `condense`, `preserve`.
- `format` (string): slides for a presentation, document for a document of sections. Default slides. One of: `slides`, `document`.
- `slides` (integer): The number of slides in all, the opening slide included: 5 makes the opening and four more. Default 8. From 1 to 20.
- `theme` (string): A theme id from list_themes. If omitted, Synclo designs a look for the presentation.
- `template` (string): A template id from list_templates.
- `language` (string): The output language, such as fr or Japanese. Default: the language of input. Up to 60 characters.
- `tone` (string): The writing style, such as warm or formal. Up to 100 characters.
- `audience` (string): The intended audience. Up to 200 characters.
- `image_style` (string): theme (the default), a style id, or a description of the images. Up to 500 characters.
- `instructions` (string): Additional instructions for the content and design. Up to 2,000 characters.

Other tools' arguments:

- `get_generation`: `generation_id` (required)
- `export_presentation`: `document_id` (required), `format` (required)
- `search_presentations`: `query`, `limit`
- `show_presentation`: `document_id` (required)
- `read_presentation`: `document_id` (required)
- `list_themes`: None
- `list_templates`: None

### Generations, credits and limits

Generation is asynchronous, as in the API. `create_presentation` returns a `generation_id` at once; `get_generation` waits up to 40 seconds for the generation before it returns, so call it again until the status is `completed` or `failed`. The same request within 10 minutes returns the first generation instead of starting another, so a retried call isn't charged twice.

A generation costs the same as in the API: 10 credits per slide from the connected workspace's balance. Failed generations aren't charged. The workspace's plan sets the most slides per presentation: 10 on Free, 20 on Plus, Pro, Max and Business. Calls are rate limited per connection, with the same limits as API requests.

### Results and errors

A tool returns its result as `text` content and as `structuredContent`. A call that can't complete returns a result with `isError: true` and a readable message. When the workspace has no credits left, the message is "This workspace is out of credits. Add credits at synclo.app."

Without a valid access token, the server returns `401` with `WWW-Authenticate: Bearer resource_metadata="https://synclo.app/.well-known/oauth-protected-resource/mcp", scope="presentations:read presentations:write"`, plus `error="invalid_token"` when the token sent is expired or revoked, was issued for another resource, or belongs to a person who is no longer a member of the workspace. A tool that needs a scope the token lacks returns `403` `insufficient_scope`.

## OAuth

Synclo is an OAuth 2.1 authorization server for the MCP server. Claude, ChatGPT and other MCP clients find it through discovery and need no setup; this section is for building your own client. Use OAuth when your app acts for people in their own Synclo workspaces, on any plan. Access tokens work on the MCP server only; to automate your own workspace through the REST API, use an API key.

### Discovery

- `GET /.well-known/oauth-protected-resource/mcp`: protected resource metadata (RFC 9728): the resource `https://synclo.app/mcp`, its authorization server and its scopes. Also served at `/.well-known/oauth-protected-resource`.
- `GET /.well-known/oauth-authorization-server`: authorization server metadata (RFC 8414):

```json
{
  "issuer": "https://synclo.app",
  "authorization_endpoint": "https://synclo.app/oauth/authorize",
  "token_endpoint": "https://synclo.app/oauth/token",
  "registration_endpoint": "https://synclo.app/oauth/register",
  "revocation_endpoint": "https://synclo.app/oauth/revoke",
  "response_types_supported": [
    "code"
  ],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token"
  ],
  "code_challenge_methods_supported": [
    "S256"
  ],
  "token_endpoint_auth_methods_supported": [
    "none"
  ],
  "revocation_endpoint_auth_methods_supported": [
    "none"
  ],
  "scopes_supported": [
    "presentations:read",
    "presentations:write"
  ],
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true,
  "service_documentation": "https://synclo.app/developers/oauth"
}
```

### Endpoints

- `GET /oauth/authorize` (Authorization): Shows the person the Allow screen; returns to `redirect_uri` with a `code`.
- `POST /oauth/token` (Token): Exchanges a code or a refresh token for tokens. Form-encoded.
- `POST /oauth/register` (Registration): Dynamic client registration (RFC 7591), for public clients.
- `POST /oauth/revoke` (Revocation): Revokes an access or refresh token (RFC 7009). Always returns `200`.

### Scopes

- `presentations:write`: Create presentations and documents in the workspace. Spends its credits. Tools: `create_presentation`.
- `presentations:read`: Find, read and show the presentations the person can open in the workspace, download them as files, list themes and templates, and check generations. Tools: `get_generation`, `export_presentation`, `search_presentations`, `show_presentation`, `read_presentation`, `list_themes`, `list_templates`.

### Client registration

Clients are public: there's no client secret, and the token endpoint's authentication method is `none`.

- Client ID Metadata Document (preferred): use an `https` URL as your `client_id`. It serves JSON with `client_id` (the same URL), `client_name` and `redirect_uris`, and optionally `logo_uri` and `client_uri`. It must respond within 5 seconds and be at most 16 KB. Synclo keeps a copy for up to 24 hours, so a change can take a day to apply.
- Dynamic client registration: `POST /oauth/register` (RFC 7591) with `client_name` and `redirect_uris`; keep the `client_id` it returns. Registration is rate limited.

```json
{
  "client_id": "https://app.example.com/oauth/client.json",
  "client_name": "Example App",
  "redirect_uris": [
    "https://app.example.com/oauth/callback"
  ],
  "logo_uri": "https://app.example.com/logo.png",
  "client_uri": "https://app.example.com"
}
```

Redirect URIs must match exactly. Loopback addresses (`http://127.0.0.1` and `http://localhost`) match on any port, for desktop and command-line clients.

### Authorization

Send the person to `/oauth/authorize` with `response_type=code`, `client_id`, `redirect_uri`, `state`, a PKCE `code_challenge` with `code_challenge_method=S256` (`plain` isn't supported), `scope` (space-separated; both scopes when omitted) and `resource=https://synclo.app/mcp` (RFC 8707; optional, and any other value returns `invalid_target`).

```text
https://synclo.app/oauth/authorize
  ?response_type=code
  &client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient.json
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
  &scope=presentations%3Aread%20presentations%3Awrite
  &state=af0ifjsldkj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fsynclo.app%2Fmcp
```

The person signs in if they need to, then sees your app's name, the address it returns to and the permissions it asks for. They pick one workspace and choose Allow or Cancel. Synclo returns to `redirect_uri` with `code` and `state`, or with `error`. Every response carries `iss=https://synclo.app` (RFC 9207): check it before using the code. A code is valid for 10 minutes and works once, with the same client, redirect URI, verifier and resource.

### Tokens

Exchange the code at `POST /oauth/token` (form-encoded) with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, `code_verifier` and `resource`. The response holds an access token (`syo_…`) that expires after 1 hour and a refresh token (`syr_…`).

```bash
curl -X POST https://synclo.app/oauth/token \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=https://app.example.com/oauth/callback \
  -d client_id=https://app.example.com/oauth/client.json \
  -d code_verifier=$CODE_VERIFIER \
  -d resource=https://synclo.app/mcp
```

```json
{
  "access_token": "syo_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "syr_…",
  "scope": "presentations:read presentations:write"
}
```

Refresh with `grant_type=refresh_token`. Each refresh returns a new refresh token, and the one sent stops working. Sending a refresh token that was already used revokes every token of that connection; the person then connects again. A connection can be refreshed for up to 90 days from the person's approval, then needs a new authorization.

```bash
curl -X POST https://synclo.app/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=$REFRESH_TOKEN \
  -d client_id=https://app.example.com/oauth/client.json \
  -d resource=https://synclo.app/mcp
```

Errors use the codes of RFC 6749 and RFC 8707: `invalid_request`, `invalid_client`, `invalid_grant`, `invalid_target` and `unsupported_grant_type`. The token endpoint is rate limited. Token responses carry `Cache-Control: no-store`. Keep tokens on your server, never in URLs or logs.

### Revocation

`POST /oauth/revoke` with `token` (RFC 7009) revokes it and always returns `200`. A person can disconnect your app at any time in Settings › Connected apps, and its tokens stop working at once. Every call checks that the person is still a member of the workspace.

## Image models

Set `images.model` to an ID below, or `auto` to let Synclo choose (included in the slide price).

| id | Name | Kind | Added credits per image |
| --- | --- | --- | --- |
| `auto` | Auto | Chosen by Synclo | None |
| `seedream-5-flash` | Seedream 5.0 Flash | Fast | None |
| `flux-2-fast` | Flux 2 Fast | Fast | None |
| `nano-banana-2-lite` | Nano Banana 2 Lite | Fast | None |
| `nano-banana-2.1` | Nano Banana 2.1 | Quality | Up to 6 |
| `gpt-image-2.5-flare` | GPT Image 2.5 Flare | Quality | Up to 8 |
| `flux-3` | Flux 3 | Quality | Up to 7 |
| `seedream-5-pro` | Seedream 5.0 Pro | Quality | Up to 7 |
| `recraft-v4.1` | Recraft V4.1 | Specialist | Up to 5 |
| `nano-banana-2.1-hd` | Nano Banana 2.1 HD | Specialist | Up to 8 |

## Image styles

Set `images.style` to `theme`, an ID below, or a description of up to 500 characters.

- `photo`: True to life
- `lifestyle`: Bright and candid
- `cinematic`: Golden hour
- `black-white`: Black and white
- `still-life`: Product close-up
- `illustration`: Flat vector
- `picture-book`: Picture book
- `watercolor`: Watercolor
- `isometric`: Isometric
- `flat-line`: Line art
- `ink-sketch`: Ink sketch
- `3d`: Soft 3D
- `paper-cutout`: Paper cut
- `felt`: Felt
- `bold-poster`: Screen-print poster
- `bauhaus`: Bauhaus
- `magazine-cutout`: Collage
- `neon-glow`: Neon
- `waves`: Silk waves
- `liquid-glass`: Liquid glass
- `paint-swirl`: Paint swirl

## Errors

Error responses: `{ "error": { "code", "message", "request_id" } }`. Use `code` in your logic; `message` may change.

| Status | code | Description |
| --- | --- | --- |
| 400 | `invalid_request` | The request body or a parameter is invalid, or a parameter is unknown. `message` names the parameter. |
| 401 | `unauthorized` | The API key is missing or invalid. The response includes `WWW-Authenticate: Bearer realm="synclo"`. |
| 402 | `insufficient_credits` | The workspace doesn't have enough credits. `error.credits` includes `balance` and `needed`. |
| 402 | `plan_required` | The workspace's plan doesn't include API access. |
| 403 | `forbidden` | The API key's owner is no longer a member of the workspace. Create a new key in Settings › API. |
| 404 | `not_found` | The generation or document doesn't exist in this workspace. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was already used with a different request body. |
| 413 | `too_large` | `input` exceeds 400,000 characters. |
| 429 | `rate_limited` | Too many requests. Retry after the number of seconds in `Retry-After`. |
| 429 | `too_many_running` | The workspace already has 2 generations in progress. Retry when one completes. |
| 500 | `internal` | An unexpected error occurred. Retry; if it persists, contact support@synclo.app with the request ID. |
| 503 | `unavailable` | The generation couldn't be started. Retry shortly; no credits were charged. |

A generation that fails after it starts returns `200` from `GET /generations/{id}` with `status: failed` and `error.code`:

- `insufficient_credits`: Another generation spent the workspace's balance before this one started.
- `unavailable`: Synclo was temporarily unavailable. Retry the request.
- `generation_failed`: The presentation couldn't be generated. Retry the request.
- `forbidden`: The API key's owner is no longer a member of the workspace.
- `timed_out`: The generation ran longer than 15 minutes.
- `internal`: An unexpected error occurred.

## Rate limits

Requests are rate limited per workspace; the limit depends on the plan. Over the limit returns `429` `rate_limited`. Polling `GET /generations/{id}` has a separate, higher limit. A workspace can run 2 generations at a time. Response headers:

- `Synclo-Request-Id`: The request's unique ID: `req_` followed by 26 characters. Error responses repeat it as `request_id`.
- `X-RateLimit-Limit`: The number of requests allowed in the current window.
- `X-RateLimit-Remaining`: The number of requests remaining in the current window.
- `X-RateLimit-Reset`: When the current window resets, as a Unix timestamp.
- `Retry-After`: On `429` responses: the number of seconds to wait before retrying.
- `Idempotent-Replayed`: On `POST /generations`: `true` when the response is the original generation for a repeated `Idempotency-Key`.

## Idempotency

Send an `Idempotency-Key` with `POST /generations`. A retry with the same key and body within 24 hours returns the original generation with `Idempotent-Replayed: true` and isn't charged again; a different body returns `409` `idempotency_conflict`.

## Scope

- Editing a presentation: The API creates presentations and documents but doesn't edit them. Generate a new one with revised `input`, or open `document.edit_url` to change it in the Synclo editor.
- Files of existing presentations: A file is generated with the presentation, through `export`. Download other files from the editor.
- Callbacks: Synclo doesn't call your server when a generation completes. Poll `GET /generations/{id}`.
- Social posts and webpages: `format` is `slides` or `document`.

## Questions

### How long does a generation take?

Most complete in one to three minutes. Poll every 5 seconds. A generation still running after 15 minutes fails with `timed_out` and isn't charged.

### How much does a generation cost?

The same as in the app: 10 credits per slide from the workspace's balance, with images on `auto` included. Other image models may add credits per image. Failed generations aren't charged.

### How do I know when a generation is complete?

Poll it. `POST /generations` returns the generation's `id` immediately; call `GET /generations/{id}` every 5 seconds until `status` is `completed` or `failed`.

### Can I use the API from Zapier, Make or n8n?

Yes, with their HTTP steps: Webhooks by Zapier (Custom Request), the HTTP module in Make, or the HTTP Request node in n8n. Send the key in the `Authorization` header, create the generation with a POST, then repeat a GET until `status` is `completed`.

### Can Claude or ChatGPT make presentations with Synclo?

Yes, through the [connector](https://synclo.app/integrations), on every plan, with no API key: the person signs in with their Synclo account. Developers can connect any MCP client to the [MCP server](https://synclo.app/developers#mcp).

### Which plans include the API?

Pro, Max and Business. Workspace admins create keys in Settings › API; a key uses its workspace's credits.

### Who can open the file link?

Anyone with the URL, for 7 days: `export.url` requires no authentication. Store it as securely as the key, or download the file and serve it yourself.

### Can I edit a generated presentation?

Yes, in the Synclo editor: every generated presentation is saved in the workspace, and `document.edit_url` opens it. The API itself doesn't edit presentations.

Support: support@synclo.app
