# API reference Source: https://docs.chroniclehq.com/api-reference Reference for Chronicle API endpoints, request formats, and responses. ## Authentication Before making API calls, you'll need to create an API key.  ## What you can do | Method | Endpoint | Description | | :----- | :--------------------------------------------------------------------------- | :----------------------------------------------------- | | GET | [/templates](/get-templates) | List available templates | | GET | [/presentations/:id](/get-presentations-id) | Fetch an existing presentation | | POST | [/presentations](/post-presentations) | Create a new presentation from a template. | | PATCH | [/presentations/:id](/patch-presentations-id) | Update presentation metadata (title only in v1). | | POST | [/uploads/create-target](/post-uploads-create-target) | Get a presigned S3 target for uploading an attachment. | | POST | [/presentations/generate](/post-presentations-generate) | Start an AI presentation generation job. | | GET | [/presentations/generate/:generationId/status](/get-generation-status) | Poll the current status of a generation job. | | POST | [/presentations/generate/:generationId/message](/post-send-followup-message) | Send a follow-up message. | ## Base URL All API requests are made to: ```text theme={null} https://api.chroniclehq.com/api/v1 ``` *** ## Common error shape ### Response fields | Field | Type | Description | | --------------- | ------ | -------------------------------------------------- | | `error` | object | Error payload. | | `error.code` | string | Machine-readable error code. | | `error.message` | string | Human-readable error message. | | `error.status` | number | HTTP status, mirrored in the body for convenience. | ```json Example theme={null} { "error": { "code": "INSUFFICIENT_PRIVILEGES", "message": "Presentation does not belong to this workspace", "status": 403 } } ``` For HTTP status semantics and common failure modes, see [Error codes](/error-codes). *** ## Related posts * [Authentication](/authentication) * [Error codes](/error-codes) * [API scope and rate limits](/api-scope-rate-limits) # API scope and rate limits Source: https://docs.chroniclehq.com/api-scope-rate-limits What the Chronicle v1 API covers, rate limits and how to work within its current scope. ## What the API covers The REST API at `https://api.chroniclehq.com/api/v1` is workspace-scoped. With a valid API key you can list templates, create and fetch presentations, update presentation metadata, upload reference files, poll job status, and send follow-up messages during generation. | Method | Endpoint | Description | | :----- | :----------------------------------------------------------------------------- | :---------------------------------------------------- | | GET | [`/templates`](/get-templates) | List available templates | | GET | [`/presentations/:id`](/get-presentations-id) | Fetch an existing presentation | | POST | [`/presentations`](/post-presentations) | Create a new presentation from a template | | POST | [`/uploads/create-target`](/post-uploads-create-target) | Get a presigned S3 target for uploading an attachment | | POST | [`/presentations/generate`](/post-presentations-generate) | Start an AI presentation generation job | | GET | [`/presentations/generate/:generationId/status`](/get-generation-status) | Poll generation job status | | POST | [`/presentations/generate/:generationId/message`](/post-send-followup-message) | Send a follow-up message during generation | | PATCH | [`/presentations/:id`](/patch-presentations-id) | Update presentation metadata (for example title) | For request and response field details, use the linked endpoint pages. ### Working outside the current scope The API does not modify slide content after generation. Title can be updated via [PATCH /presentations/:id](/patch-presentations-id); for any other content change, generate a new presentation with the revised input. The API uses an asynchronous polling model. After creating a generation, poll [GET /presentations/generate/:generationId/status](/get-generation-status) until the status is completed or failed. A completed presentation includes a URL for viewing the output. From there you can export, present as needed. Images, charts, and diagrams can be prompted through your input text. For more predictable layouts, use the template-based generation. *** ## Rate limits Chronicle may apply rate limits per API key. Example policy: `20 requests per minute per key` When rate limited, the API should return `429 Too Many Requests`. Example response: ```json Example response theme={null} { "error": { "code": "API_KEY_RATE_LIMITED", "message": "Rate limit exceeded", "status": 429 } } ``` For how `429` fits into client behavior, see [Error codes](/error-codes). *** ## Polling generation jobs Generation workflows are asynchronous. When polling: * Poll at a reasonable interval * Stop polling when the job reaches a terminal state * Back off on repeated failures * Handle timeouts gracefully See [GET /presentations/generate/:generationId/status](/get-generation-status) for the status response shape. *** ## Production best practices * Log request IDs if available * Log status codes and error types * Retry only transient failures * Surface useful error messages to operators * Avoid aggressive polling loops **Tip:** If your integration performs generation, most reliability issues come from polling too aggressively or not handling transient failures well. *** ## Related posts * [Error codes](/error-codes) * [API reference](/api-reference) * [Troubleshooting](/troubleshooting) # Authentication Source: https://docs.chroniclehq.com/authentication Authenticate to the Chronicle API with a workspace-scoped API key. API keys let your application create, generate, fetch, and update presentations on behalf of a Chronicle workspace. When you create a key: * It belongs to a specific workspace * It can access resources in that workspace *** ## Create an API key Go to Settings → API in the [Chronicle](https://app.chroniclehq.com) web app. Click Create API Key Copy the key immediately, it will only be shown once. Store it in a secure location such as an environment variable or secrets manager. ## Use the API key Include the key in the header with every request: Supported headers: * `Authorization: Bearer ` * `x-api-key: ` Use one method consistently across your integration. ### Using `Authorization` ```text theme={null} curl -X GET "https://api.chroniclehq.com/api/v1/templates" \ -H "Authorization: Bearer $CHRONICLE_API_KEY" ``` ### Using `x-api-key` ```text theme={null} curl -X GET "https://api.chroniclehq.com/api/v1/templates" \ -H "x-api-key: $CHRONICLE_API_KEY" ``` *** ## Store keys securely * Use API keys only in server-side code * Never expose keys in frontend applications * Never commit keys to source control * Rotate keys periodically * Revoke keys that are no longer needed * Use separate keys per environment when possible Keep your API keys secure and never share them publicly. Each key provides full access to your Chronicle workspace. If a key is compromised, revoke it immediately from the settings page. *** ## Authentication errors Returned when: * The key is missing * The key is invalid * The key is expired * The key is revoked * The auth header is malformed Returned when the key is valid but the resource is outside the key's workspace. Each API key is bound to a single workspace and can only see resources in that workspace. *** ## FAQs Use an API key for the public REST API. Create the key in Chronicle, store it securely, and send it with requests to `/api/v1/...`. Send it as either: * `Authorization: Bearer ` * `x-api-key: ` Use one method consistently across your integration. For security reasons, Chronicle only shows the raw API key once. Copy it when you create it and store it in your secret manager. If you lose it, create a new key. A `403` means your key was accepted, but the action is not permitted. The most common cause is that the resource belongs to a different workspace than the one your key is bound to. Each key is workspace-scoped and can only see resources in its own workspace. Yes. Requests are rate limited per API key. If you hit the limit, reduce request frequency and retry with backoff. [Learn more](/api-scope-rate-limits) Keys are revoked rather than hard-deleted. This preserves audit visibility. Once revoked, the key can no longer be used. *** ## Related posts * [Quickstart](/quickstart) * [API reference](/api-reference) * [Error codes](/error-codes) # Changelog Source: https://docs.chroniclehq.com/changelog ## MCP — Standalone generation & file attachments * **MCP `generate_presentation` accepts an optional `template_id`.** Omit it for the Standalone path — Chronicle drafts a storyline first, then slides. * **New MCP tool `create_upload_target`** for issuing presigned S3 upload targets so the host can attach files (PDF, PPTX, images, text) to a generation. * **`attachments[]` accepted on MCP `generate_presentation` and `send_followup_message`** — pass entries from `create_upload_target` to ground the generation on a file's contents. See the [MCP setup guide](/mcp-guide) for the full attachment flow and supported formats. ## Standalone generation, non-interactive mode, structured params * `template_id` is now optional on `POST /presentations/generate`. When omitted, Chronicle uses the Standalone flow — storyline outline first, then slides. * `non_interactive: true` body flag: Chronicle never pauses to ask clarifying questions; auto-proceeds end-to-end. Built for automation integrations like n8n, Zapier, Make, and MCP. * Structured storyline params (Standalone path with `non_interactive: true`): `narrative_type`, `rewrite_style`, `section_count`, `language`. * `GET /presentations/generate/:generationId/status` now returns `phase: 'storyline' | 'slides'` for Standalone generations so clients can show two-phase progress. ## File attachments * `POST /uploads/create-target` for uploading reference files (PDF, PPTX, images, text) used to ground generation * `attachments[]` accepted on `POST /presentations/generate` and `POST /presentations/generate/:generationId/message` ## Initial public release of the Chronicle API. * API key authentication for workspace admins * `GET /templates` for listing available templates * `GET /presentations/:id` for fetching an existing presentation * `POST /presentations` for creating a deck from a template * `PATCH /presentations/:id` for updating presentation metadata (title only) * `POST /presentations/generate` for generating a deck from a prompt * `GET /presentations/generate/:generationId/status` for polling a generation job * `POST /presentations/generate/:generationId/message` for sending follow-up answers when generation requests clarification # Error codes Source: https://docs.chroniclehq.com/error-codes Chronicle API error response shape and common HTTP status codes. ## Error format Chronicle API errors return a consistent shape. | Field | Type | Description | | --------------- | ------ | -------------------------------------------------- | | `error` | object | Error payload. | | `error.code` | string | Machine-readable error code. | | `error.message` | string | Human-readable error message. | | `error.status` | number | HTTP status, mirrored in the body for convenience. | ```json theme={null} { "error": { "code": "BAD_REQUEST", "message": "template_id is required", "status": 400 } } ``` *** ## Common status codes The request is malformed or missing required fields. `error.code` is one of `BAD_REQUEST` or `WORKSPACE_ID_REQUIRED` (MCP/JWT path with no `workspace_id` in body or query). Common causes: * Missing required fields * Invalid JSON * Invalid parameter values * Missing `workspace_id` on JWT-authenticated MCP requests Authentication failed. `error.code` is one of `API_KEY_INVALID`, `API_KEY_EXPIRED`, `API_KEY_REVOKED`, `UNAUTHORIZED` (missing/malformed header), `JWT_INVALID`, `JWT_EXPIRED`, `JWT_WRONG_AUDIENCE`, or `JWT_SIGNATURE_INVALID`. The `JWT_*` codes apply on the MCP/JWT auth path. Common causes: * Missing API key * Invalid API key * Expired API key * Revoked API key * Malformed auth header * Expired or invalid MCP JWT — reconnect via the OAuth flow to refresh Workspace usage limits hit. `error.code` is one of `PRICING_REMIX_OVER_LIMIT` or `PRICING_DOCUMENT_LIMIT_REACHED`. Surface a clear billing/upgrade message to the user — retrying will not help until the limit is lifted. The API key or JWT is valid, but the resource is outside the caller's reach. `error.code` is one of `INSUFFICIENT_PRIVILEGES` (chr\_\* key targeting a different workspace), `WORKSPACE_ACCESS_DENIED` (JWT user lacks workspace membership), or `MCP_NOT_ENABLED` (user not in the MCP feature allowlist; only `list_workspaces` is usable in this state). The requested resource does not exist or is not accessible to the workspace. `error.code` is one of `DOCUMENT_NOT_FOUND`, `TEMPLATE_NOT_FOUND`, `WORKSPACE_NOT_FOUND`, or `NOT_FOUND` (unknown generation job). Common causes: * Unknown template ID * Unknown presentation ID * Unknown generation job ID The request exceeded the per-API-key rate limit. `error.code` is `API_KEY_RATE_LIMITED`. Back off and retry. A temporary server-side or upstream failure occurred. `error.code` is `INTERNAL_ERROR` or `UNKNOWN_INTERNAL_ERROR`. *** ## Retry guidance Retry these cases: * `429 Too Many Requests` * `500 Internal Server Error` * `502 Bad Gateway` * `503 Service Unavailable` * `504 Gateway Timeout` Do not blindly retry these cases: * `400 Bad Request` * `401 Unauthorized` * `402 Payment Required` * `403 Forbidden` * `404 Not Found` Surface these to the user — retrying without changing the request will not help. *** ## Exponential backoff Use exponential backoff with jitter when retrying. Pseudo-logic: ```text theme={null} attempt 1: wait 1s attempt 2: wait 2s attempt 3: wait 4s attempt 4: wait 8s ``` *** ## Related posts * [API scope and rate limits](/api-scope-rate-limits) * [Authentication](/authentication) * [Troubleshooting](/troubleshooting) # GET /presentations/generate/:generationId/status Source: https://docs.chroniclehq.com/get-generation-status GET /presentations/generate/:generationId/status Returns the current state of a generation job. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/generate/gen_123/status" \ -H "Authorization: Bearer $API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/generate/gen_123/status", { headers: { Authorization: `Bearer ${API_KEY}`, }, }, ); const data = await response.json(); console.log(data); ``` ### Parameters The generation job ID ### Response ```json generating (standalone) theme={null} { "generation_id": "gen_123", "status": "generating", "phase": "storyline", "tokens_used": 1280 } ``` ```json generating theme={null} { "generation_id": "gen_123", "status": "generating", "progress": { "slides_total": 9, "slides_completed": 3, "current_step": "Creating slide 4 of 9" }, "tokens_used": 12450 } ``` ```json awaiting input theme={null} { "generation_id": "gen_123", "status": "awaiting_input", "message": "What does your company do?" } ``` ```json completed theme={null} { "generation_id": "gen_123", "status": "completed", "presentation": { "id": "pres_123", "title": "Agency pitch deck", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_123", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:00:00.000Z" }, "tokens_used": 38200 } ``` ```json failed theme={null} { "generation_id": "gen_123", "status": "failed", "error": "Generation timed out" } ``` #### Common fields Generation job ID Current generation status: one of `generating`, `awaiting_input`, `completed`, `failed`. #### When status = "generating" Optional progress object. Present once Chronicle has begun generating slides. Total number of slides expected Number of slides completed so far Human-readable progress message Optional. Present only for Standalone generations (when `template_id` was omitted on the original request). One of `storyline` (Chronicle is planning the deck outline) or `slides` (Chronicle is generating the slides from the storyline). Use this to give users finer-grained progress feedback than the time-based stage progression. Optional running token total for this generation. Present when non-zero. #### When status = "awaiting\_input" Clarifying question or follow-up prompt from Chronicle. Reply via [POST /presentations/generate/:generationId/message](/post-send-followup-message). #### When status = "completed" The materialized presentation. Same shape as [GET /presentations/:id](/get-presentations-id). Presentation ID Presentation title Workspace ID that owns the presentation Presentation slides Whether the presentation is public Canonical presentation URL ISO timestamp when the presentation was created ISO timestamp when the presentation was last updated Optional total tokens consumed by the generation. Present when non-zero. #### When status = "failed" Human-readable failure reason (for example `Generation timed out` or a propagated upstream error message). *** ## FAQs Poll at a short interval, such as every few seconds. Avoid polling in a tight loop. Your client should handle these statuses: * `generating` * `completed` * `failed` * `awaiting_input` When generation completes, Chronicle returns the final presentation object. That response includes the information you need to open or continue working with the presentation. Treat `failed` as a terminal state and surface the error clearly in your app or logs. Do not continue polling after generation has failed. Yes. The status response can include progress details while the presentation is being generated. Use those fields to give users better feedback than a generic loading state. For Standalone generations (no `template_id`), Chronicle works in two phases: it first generates the storyline outline, then it generates the slides from that outline. The `phase` field tells you which one is currently active so your UI can show a more accurate progress indicator. Template-based generations don't emit a `phase` — they're single-phase from the API's perspective. *** ## Related posts * [POST /presentations/generate](/post-presentations-generate) * [POST /presentations/generate/:generationId/message](/post-send-followup-message) * [API scope and rate limits](/api-scope-rate-limits) # Get help Source: https://docs.chroniclehq.com/get-help Support when you need it ### Before contacting support Check our [Troubleshooting guide](/troubleshooting) to see if there's an answer to your issue. If you're still blocked, include as much of the following as possible: * the endpoint you called * the full HTTP status code * the response body * timestamp of the request * workspace identifier * API key creation time or whether the key was recently rotated * generation, template, or presentation identifiers involved * whether the issue is reproducible This makes it much faster to diagnose auth issues, generation failures, template problems, and MCP setup issues. ### Questions, quick feedback, and debugging Join the [Chronicle Slack community channel](https://join.slack.com/t/chroniclecommunity/shared_invite/zt-3wsmivyl2-q3~wha_E~BLRBi6Kz7OAEg). Use the `#bugs-and-feedback` channel and a member of Chronicle's team will respond. ### Contact support If you need more help, you can reach out to our [Support](mailto:support@chroniclehq.com) team. # GET /presentations/:id Source: https://docs.chroniclehq.com/get-presentations-id GET /presentations/:id Fetches a presentation by ID. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/pres_123" \ -H "Authorization: Bearer $API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/pres_123", { headers: { Authorization: `Bearer ${API_KEY}`, }, } ); const data = await response.json(); console.log(data); ``` ### Parameters The presentation ID ### Response ```json Response theme={null} { "id": "pres_123", "title": "Q2 business review", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_123", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:00:00.000Z" } ``` Presentation ID Presentation title Workspace ID that owns the presentation Presentation slides Whether the presentation is public Canonical presentation URL ISO timestamp when the presentation was created ISO timestamp when the presentation was last updated *** ## FAQs This endpoint expects a Chronicle presentation ID. Do not send a share URL in its place. Your API key can only access resources in its own workspace. If the presentation belongs to a different workspace, or is stored in private drafts, the request returns `403`. REST API responses use `snake_case`. Use that field format in your client code and examples. The response includes a Chronicle URL for the presentation. You can use that URL to open the presentation directly in Chronicle. *** ## Related posts * [PATCH /presentations/:id](/patch-presentations-id) * [POST /presentations](/post-presentations) * [Error codes](/error-codes) # GET /templates Source: https://docs.chroniclehq.com/get-templates GET /templates Returns published public templates and templates available to the workspace tied to your API key. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/templates" \ -H "Authorization: Bearer $API_KEY" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/templates", { headers: { Authorization: `Bearer ${API_KEY}`, }, } ); const data = await response.json(); console.log(data); ``` The workspace is inferred from your API key. No query parameters are required. ### Response ```json Response theme={null} { "templates": [ { "id": "tpl_123", "name": "Sales pitch", "description": "A pitch template for sales discovery calls", "category": ["sales", "discovery"], "preview_url": "https://templates.chroniclehq.com/sales-pitch.png", "sections": [] } ] } ``` List of available templates Template ID Template name Template description Template categories. Empty array if uncategorized. Absolute preview image URL on `templates.chroniclehq.com`. `null` when the template has no preview. Template slides *** ## FAQs No. Workspace scope is inferred from your API key — every key belongs to exactly one workspace. The response contains the public templates plus templates accessible to that workspace. The endpoint returns public templates plus templates accessible to the workspace. Hidden and personal templates are excluded. Use them as inputs to presentation creation or generation flows. This endpoint is the best way to discover valid templates before making those requests. *** ## Related posts * [POST /presentations](/post-presentations) * [Authentication](/authentication) * [Error codes](/error-codes) # MCP setup Source: https://docs.chroniclehq.com/mcp-guide Bring the power of Chronicle into your AI tools. The Chronicle MCP server lets AI tools like Claude, ChatGPT, and Gemini access your Chronicle workspace to generate presentations directly. ## Quick reference * Works with any AI tool that supports the MCP (Model Context Protocol). * Available on Pro, Plus, and Max Chronicle plans. Generations charge credits. How It Works ## Installation In Claude.ai, open **Settings** → **Connectors**, then click **Add custom connector**. Fill in: * **Name**: `Chronicle` * **Remote MCP server URL**: `https://mcp.chroniclehq.com` Click **Add** to save. On the Connectors page, click **Connect** next to Chronicle. A browser window opens — complete the Chronicle sign-in and the connector activates. Start a new chat and ask Claude to generate a presentation. Claude will call Chronicle automatically. For best results: * specify format (presentation, deck, proposal) * be specific about structure ("create a 10 slide sales proposal using the following transcript") Run in your terminal: ```bash theme={null} claude mcp add --transport http chronicle https://mcp.chroniclehq.com ``` Add `--scope user` if you want Chronicle available across every project on your machine. Start Claude Code with `claude`, then ask anything that triggers a Chronicle tool (e.g. *"list my Chronicle workspaces"*). The first call opens a browser window — complete the Chronicle sign-in and the tool call resumes automatically. Claude Code can now create a presentation with Chronicle. For best results: * specify format (presentation, deck, proposal) * be specific about structure ("create a 10 slide sales proposal using the following transcript") *** ## Using the MCP server Once configured, your AI assistant can access Chronicle automatically when you prompt or paste a Chronicle link into chat. You can get the link for any presentation by copying the url in your browser address bar. If you call the REST API directly (for example to poll generation status), see [API scope and rate limits](/api-scope-rate-limits) for rate limits, retries, and polling guidance. *** ## Available tools Every tool except `list_workspaces` takes `workspace_id` as input. Call `list_workspaces` first to discover which `workspace_id` to use. | Tool | Input/s | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `list_workspaces` | *(none)* | Returns workspaces the authenticated user can target. Call this first to obtain `workspace_id` for every other tool. The response also includes `mcp_enabled`; if `false`, all subsequent tool calls are rejected with `MCP_NOT_ENABLED`. | | `get_templates` | `workspace_id` | Returns templates available to the workspace. | | `get_presentation` | `workspace_id`, `presentation_id` | Fetches an existing presentation. | | `create_presentation` | `workspace_id`, `template_id`, `selected_section_ids` (optional) | Creates a presentation from a template, no AI involved. | | `create_upload_target` | `workspace_id`, `file_name`, `content_type`, `declared_file_size` | Issues a presigned S3 upload target for a file attachment. The host uploads the bytes directly, then references the file on `generate_presentation` or `send_followup_message`. See [Attachments](#attachments) below. | | `generate_presentation` | `workspace_id`, `prompt`, `template_id` (optional), `selected_section_ids` (optional), `attachments` (optional) | Starts an AI generation and returns `status="generating"` immediately with a `generation_id`. Pass `template_id` to follow a known template, or omit it for the **Standalone** path where Chronicle drafts a storyline first, then slides. Pass `attachments[]` (from `create_upload_target`) to ground the generation on files. Poll `get_generation_status` until terminal. | | `send_followup_message` | `workspace_id`, `generation_id`, `content`, `attachments` (optional) | Replies to an in-flight generation that returned `awaiting_input`. Returns `status="generating"` immediately; poll `get_generation_status` until the next terminal state. Optional `attachments[]` lets the user introduce files mid-conversation. | | `get_generation_status` | `workspace_id`, `generation_id` | Polls a generation's current state. Returns `generating`, `awaiting_input`, `completed`, or `failed`. Each call holds open for up to \~50 s server-side; if still non-terminal, call again. | `generate_presentation` and `send_followup_message` return `status="generating"` immediately. The caller must then poll `get_generation_status` until the state is terminal (`completed`, `awaiting_input`, or `failed`). Each `get_generation_status` call holds open for up to \~50 s server-side, so consecutive calls cover long generations without keeping any single MCP request alive. Pass user prompts and follow-up answers through verbatim; Chronicle's AI will ask for missing context itself. *** ## Attachments Ground a generation on user files. Mention a file path in your chat (PDFs, decks, images, text) — your AI client handles the upload to Chronicle. **Supported formats:** `.txt`, `.md`, `.pdf`, `.pptx`, `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`. Maximum **50 MB** per file. *** ## Example prompts For best results: specify format (presentation, deck, proposal), be specific about structure ("create a 10 slide sales proposal using the following transcript") ```text theme={null} Create a 10-slide pitch deck for a new festival management tool called Stagecraft. ``` ```text theme={null} Use the YC Pitch Deck template to create a pitch for my AI startup. ``` ```text theme={null} Generate a Q1 review deck. Use this PDF for context: /Users/me/Downloads/q1-report.pdf ``` *** ## Troubleshooting If you’re seeing authentication errors: * Try re-authenticating by reconnecting to the MCP server in your client * Check that you have the correct permissions for the project you’re trying to access * Make sure your browser session is active when authenticating Verify: * The component/page exists in your Chronicle workspace * The name or URL matches exactly * You have access to the presentation If the MCP server is unreachable: * Check your internet connection * Verify the URL is `https://mcp.chroniclehq.com` * Reach out to the Chronicle team for support Check your available credits via Settings → Billing in Chronicle. You can purchase more tokens or upgrade plan. *** ## FAQs Chronicle’s MCP integration supports template discovery, presentation retrieval, presentation creation, and AI-powered generation workflows. It lets AI clients create and work with Chronicle presentations directly. Some requests need clarification before Chronicle can generate the right presentation. Your MCP client should present the question to the user and send the answer back before continuing. No. Treat it as a multi-step workflow. A complete flow can include generation, polling, one or more follow-up messages, and final completion. Yes. Long-running generation flows can time out. Your client should handle timeouts gracefully and let the user retry when appropriate. Follow the setup guide for your MCP client and use the Chronicle connection flow documented for that environment. Authentication steps can vary by client. Yes. Call `create_upload_target` to get a presigned S3 URL, upload the bytes from the host, then pass an entry in `attachments[]` on `generate_presentation` or `send_followup_message`. See [Attachments](#attachments) for the full flow and supported formats. *** ## Related posts * [API reference](/api-reference) * [Error codes](/error-codes) * [Troubleshooting](/troubleshooting) # Chronicle API Source: https://docs.chroniclehq.com/overview Generate, fetch, and edit presentations from your own products, workflows, and AI agents. ## What you can do * create a presentation from a template * generate a presentation from a prompt * fetch an existing presentation * update presentation metadata * list the templates available to a workspace - internal tools that generate decks on demand - automated workflows in Zapier, Make, or n8n - AI assistants that create presentations for users - product experiences that embed Chronicle-generated output - pipelines that turn structured business data into polished presentations * A sales tool sends account notes to Chronicle and gets back a shareable presentation. * A workflow automation creates a weekly business review from a spreadsheet. * An internal app lets teams choose a template and generate a deck from a form. * An AI agent creates a presentation for a user inside a chat interface. ## API or MCP? * building a product integration * direct HTTP access * wiring Chronicle into backend services or automations * predictable programmatic control * AI tools like Claude or ChatGPT to use Chronicle as a capability * agent-driven presentation creation * expose Chronicle as a tool than build directly against raw endpoints MCP is the agent-friendly layer, while the API is the integration layer underneath it. *** ## How it works 1. Authenticate with a workspace-scoped API key 2. Choose a template or generation flow 3. Generate a presentation 4. Receive a Chronicle presentation and URL 5. Fetch, update, or pass that result into the next step of your workflow Some endpoints respond immediately, while generation workflows may be asynchronous and require polling. *** ## Core concepts API access is scoped to a Chronicle workspace. Templates, presentations, and permissions are tied to that workspace. Requests are authenticated with API keys created by a workspace admin. Keys should be stored securely and used from trusted backend environments. Templates define the starting structure and design of a presentation. They’re often the fastest way to create consistent output through the API. A presentation is the final Chronicle output your workflow creates or updates. API responses include a presentation `id` and a `url` your users can open directly in Chronicle. Generation workflows process the request asynchronously. The API returns a `generation_id` that you can poll until the presentation is ready. *** ## Design principles for the API When building with Chronicle, it helps to think of the API as optimized for: * Fast time to first successful presentation * Template-based creation for consistency * Generation workflows for flexible input * Shareable output that fits into existing tools * Workspace-level security and access control *** ## What to read next Make your first request Connect Chronicle to AI agents Endpoints and request formats Resolve common issues # PATCH /presentations/:id Source: https://docs.chroniclehq.com/patch-presentations-id PATCH /presentations/:id Updates presentation metadata. In the current version, only title is supported. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/pres_123" \ -X PATCH \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Updated deck title" }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/pres_123", { method: "PATCH", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ title: "Updated deck title", }), } ); const data = await response.json(); console.log(data); ``` ### Parameters The presentation ID Updated presentation title ### Response ```json Response theme={null} { "id": "pres_123", "title": "Updated deck title", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_123", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:05:00.000Z" } ``` Presentation ID Presentation title Workspace ID that owns the presentation Presentation slides Whether the presentation is public Canonical presentation URL ISO timestamp when the presentation was created ISO timestamp when the presentation was last updated *** ## FAQs In v1, this endpoint supports updating the presentation title. Other presentation fields are not editable through this route. Chronicle treats an empty body as a no-op and returns the current presentation state. If you want to make a change, include a supported field in the request body. No. This endpoint does not update presentation content. Use it only for supported presentation properties. *** ## Related posts * [GET /presentations/:id](/get-presentations-id) * [POST /presentations](/post-presentations) * [Error codes](/error-codes) # POST /presentations Source: https://docs.chroniclehq.com/post-presentations POST /presentations Creates a new presentation from an existing template. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations" \ -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_id": "tpl_123" }' ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.chroniclehq.com/api/v1/presentations", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template_id: "tpl_123", }), }); const data = await response.json(); console.log(data); ``` ### Parameters The template to create from Optional subset of section IDs from the template to include. If omitted, all sections are included. ### Response A successful request returns `201 Created`. The workspace is inferred from your API key. ```json Response theme={null} { "id": "pres_123", "title": "Q2 Business Review", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_123", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:00:00.000Z" } ``` Presentation ID Presentation title Workspace ID that owns the presentation Presentation slides Whether the presentation is public Canonical presentation URL ISO timestamp when the presentation was created ISO timestamp when the presentation was last updated *** ## FAQs This endpoint creates a presentation from an existing template. Use it when you already know which template you want to use. No. This endpoint creates presentations from templates. If you want Chronicle to generate a presentation from a prompt, use the [generate](/post-presentations-generate) endpoint instead. No. Fetch templates first, then use the returned template ID. That keeps your integration aligned with the templates available to the workspace. *** ## Related posts * [GET /templates](/get-templates) * [GET /presentations/:id](/get-presentations-id) * [Error codes](/error-codes) # POST /presentations/generate Source: https://docs.chroniclehq.com/post-presentations-generate POST /presentations/generate Starts an asynchronous AI presentation generation job. ```shellscript cURL — template theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/generate" \ -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_id": "tpl_123", "prompt": "Create a sales pitch deck for Chronicle aimed at agencies", "attachments": [ { "id": "att_123", "url": "https://chronicle-attachments.s3.us-east-1.amazonaws.com/...", "file_name": "agency-research.pdf", "type": "application/pdf" } ] }' ``` ```javascript JavaScript — template theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/generate", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template_id: "tpl_123", prompt: "Create a sales pitch deck for Chronicle aimed at agencies", attachments: [ { id: "att_123", url: "https://chronicle-attachments.s3.us-east-1.amazonaws.com/...", file_name: "agency-research.pdf", type: "application/pdf", }, ], }), }, ); const data = await response.json(); console.log(data); ``` ```shellscript cURL — standalone theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/generate" \ -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Create a research deck on renewable-energy adoption in EU manufacturing", "non_interactive": true, "narrative_type": "Research", "section_count": 7, "language": "uk" }' ``` ```javascript JavaScript — standalone theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/generate", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "Create a research deck on renewable-energy adoption in EU manufacturing", non_interactive: true, narrative_type: "Research", section_count: 7, language: "uk", }), }, ); const data = await response.json(); console.log(data); ``` ### Parameters Instruction for what to generate. Template that seeds the generation. If omitted, Chronicle uses the Standalone flow — a storyline outline is generated first, then slides. This is the same path the product UI uses when a user creates a deck from a prompt without picking a template. Optional subset of section IDs from the template to include. If omitted, all sections are included. Ignored on the Standalone path. Optional list of files to ground the generation in. Each attachment is uploaded via [POST /uploads/create-target](/post-uploads-create-target) first; the returned `download_url` becomes this object's `url`. When provided, every field on each entry (`id`, `url`, `file_name`, `type`) is required. When true, Chronicle never pauses to ask clarifying questions and auto-proceeds end-to-end. Required for automation integrations like n8n, Zapier, Make, or MCP — anything that can't pause mid-workflow to answer follow-up questions. When false (default), Chronicle may return `awaiting_input` and wait for a reply via [POST /presentations/generate/:generationId/message](/post-send-followup-message). Storyline archetype. One of `Pitch`, `Showcase`, `Sales`, `Proposal`, `Research`, `Guide`, `Meeting`, `Portfolio`, `Auto`. Defaults to `Auto` (Chronicle picks). Only honored on the Standalone path (no `template_id`) with `non_interactive: true`. How aggressively Chronicle rewrites your input prompt and any attachment content. One of `Strong`, `Subtle`, `Preserve`. Defaults to `Subtle`. Only honored on the Standalone path with `non_interactive: true`. Target number of sections in the storyline. Pass a positive integer (for example `5`) or the string `"auto"` to let Chronicle decide. Defaults to `"auto"`. Only honored on the Standalone path with `non_interactive: true`. Output language. One of `us`, `uk`, `es`, `fr`, `de`, `it`, `pt`, `cn`, `in`, `ja`, `ko`, `ar`, `hi`, `bn`. Defaults to `us` (American English). Only honored on the Standalone path with `non_interactive: true`. ### Response A successful request returns `202 Accepted`. The workspace is inferred from your API key. ```json Response theme={null} { "generation_id": "gen_123", "status": "generating", "poll_url": "/api/v1/presentations/generate/gen_123/status" } ``` Generation job ID Current generation status Relative URL for checking generation status *** ## FAQs No. Generation is asynchronous. The endpoint starts a generation job and returns a `generation_id`. Use that ID to check status. Use `POST /api/v1/presentations` to create from a template. Use `POST /api/v1/presentations/generate` to generate a presentation from a prompt. Yes — by default. Chronicle can pause generation and return `awaiting_input` with a clarifying question. Set `non_interactive: true` on the request to skip this entirely. Chronicle will pick reasonable defaults and proceed silently. Required for automation tools that can't pause mid-workflow. No. When `template_id` is omitted, Chronicle uses the Standalone flow — it generates a storyline outline first, then slides. This is the same path the product UI uses when you create a deck from a prompt without picking a template. Pass `template_id` when you want Chronicle to stamp out a deck that matches a specific template's structure. Only when `non_interactive: true` AND `template_id` is omitted (the Standalone path). Outside that combination, Chronicle ignores `narrative_type`, `rewrite_style`, `section_count`, and `language` — the template defines structure in template-mode, and the interactive flow lets users clarify their preferences in chat. Yes. Chronicle can reject a generation request before processing begins, including when usage limits are exceeded. Handle these responses separately from authentication errors. Yes. Upload each file via [POST /uploads/create-target](/post-uploads-create-target) first, then pass the captured `download_url` (with `id`, `file_name`, and `type`) in the `attachments` array. *** ## Related posts * [POST /uploads/create-target](/post-uploads-create-target) * [GET /presentations/generate/:generationId/status](/get-generation-status) * [POST /presentations/generate/:generationId/message](/post-send-followup-message) * [API scope and rate limits](/api-scope-rate-limits) # POST /presentations/generate/:generationId/message Source: https://docs.chroniclehq.com/post-send-followup-message POST /presentations/generate/:generationId/message Sends a follow-up message within an active generation job. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/generate/gen_123/message" \ -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "We help teams turn ideas into polished interactive presentations.", "attachments": [ { "id": "att_456", "url": "https://chronicle-attachments.s3.us-east-1.amazonaws.com/...", "file_name": "case-study.pdf", "type": "application/pdf" } ] }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/presentations/generate/gen_123/message", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ content: "We help teams turn ideas into polished interactive presentations.", attachments: [ { id: "att_456", url: "https://chronicle-attachments.s3.us-east-1.amazonaws.com/...", file_name: "case-study.pdf", type: "application/pdf", }, ], }), }, ); const data = await response.json(); console.log(data); ``` ### Parameters The generation job ID The user's reply to Chronicle's clarifying question. Sent as-is into the same generation conversation. Optional list of files to attach to the follow-up message. Same shape as on [POST /presentations/generate](/post-presentations-generate) — upload each file via [POST /uploads/create-target](/post-uploads-create-target) first, then pass the returned `download_url` along with `id`, `file_name`, and `type`. All four fields are required on each entry. ### Response After accepting the follow-up, resume polling [`GET /presentations/generate/:generationId/status`](/get-generation-status) on the same `generationId`. ```json Response theme={null} { "status": "generating" } ``` Confirms the follow-up was accepted and generation is resuming. Continue polling the status endpoint with the original `generationId`. *** ## FAQs Call this endpoint when the generation status is `awaiting_input`. It sends the user’s response back into the same generation flow. Yes. Some generation flows require multiple follow-up messages before completion. Continue until the status changes to `completed` or `failed`. Resume polling the generation status endpoint. That tells you whether generation is continuing, needs more input, or has finished. Yes. Upload each file via [POST /uploads/create-target](/post-uploads-create-target) first, then pass the captured `download_url` (with `id`, `file_name`, and `type`) in the `attachments` array — same shape as on `POST /presentations/generate`. *** ## Related posts * [POST /uploads/create-target](/post-uploads-create-target) * [GET /presentations/generate/:generationId/status](/get-generation-status) * [POST /presentations/generate](/post-presentations-generate) * [API scope and rate limits](/api-scope-rate-limits) # POST /uploads/create-target Source: https://docs.chroniclehq.com/post-uploads-create-target POST /uploads/create-target Returns a presigned S3 POST so you can upload an attachment for use in a generation. ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/uploads/create-target" \ -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_name": "research-notes.pdf", "content_type": "application/pdf", "declared_file_size": 524288 }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.chroniclehq.com/api/v1/uploads/create-target", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ file_name: "research-notes.pdf", content_type: "application/pdf", declared_file_size: 524288, }), }, ); const data = await response.json(); console.log(data); ``` ### Parameters The file's original name, including extension. Used for display and to derive the file type downstream. The MIME type of the file (for example `application/pdf`). Must be in the allowed list — see the FAQ below. The size of the file in bytes. Capped at 50 MB; the cap is also enforced by S3 on the upload itself. ### Response ```json Response theme={null} { "url": "https://chronicle-attachments.s3.us-east-1.amazonaws.com/", "fields": [ { "name": "Content-Type", "value": "application/pdf" }, { "name": "key", "value": "file-attachments/ws_123/abc-research-notes.pdf" }, { "name": "Policy", "value": "..." }, { "name": "X-Amz-Signature", "value": "..." } ], "path": "file-attachments/ws_123/abc-research-notes.pdf", "download_url": "https://chronicle-attachments.s3.us-east-1.amazonaws.com/file-attachments/ws_123/abc-research-notes.pdf?X-Amz-..." } ``` The S3 endpoint to `POST` the file to. Form fields that must be included in the multipart upload, in order, before the `file` field. Each entry has `name` and `value`. The S3 object key the file will be stored under. A signed `GET` URL valid for 7 days. Pass this as the `url` on each attachment object when calling [POST /presentations/generate](/post-presentations-generate) or [POST /presentations/generate/:generationId/message](/post-send-followup-message). *** ## Uploading the file After this endpoint returns, post the file directly to S3 using the returned `url` and `fields`. The `file` field must come last in the form. ```shellscript cURL theme={null} curl "$URL" \ -F "Content-Type=application/pdf" \ -F "key=file-attachments/ws_123/abc-research-notes.pdf" \ -F "Policy=..." \ -F "X-Amz-Signature=..." \ -F "file=@research-notes.pdf" ``` ```javascript JavaScript theme={null} const form = new FormData(); for (const f of target.fields) form.append(f.name, f.value); form.append("file", file); // must be last const upload = await fetch(target.url, { method: "POST", body: form, }); // upload.status === 204 on success ``` A successful upload returns `204 No Content`. **Do not** send an `Authorization` header on this request — S3 will reject it. Once uploaded, reference the file in a generation by passing the captured `download_url`: ```json Attachment object theme={null} { "id": "att_123", "url": "", "file_name": "research-notes.pdf", "type": "application/pdf" } ``` *** ## FAQs The presigned POST sends the file bytes straight to S3, so large attachments don't traverse the Chronicle API. The Chronicle endpoint stays lightweight (just metadata) and the upload itself can stream through S3's regional ingress. `.txt`, `.md`, `.pdf`, `.pptx`, and images (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`). Sending `content_type` outside this list returns `FILE_UPLOAD_ERROR`. 50 MB per file. Larger files are rejected with `FILE_UPLOAD_ERROR`. The cap is enforced both at this endpoint (against `declared_file_size`) and at S3 (against the actual upload). 7 days. As long as you reference it in a generation within that window, it'll work. After expiry you'll need to upload the file again. The workspace tied to your API key. Attachments are scoped to that workspace and aren't visible to other workspaces. *** ## Related posts * [POST /presentations/generate](/post-presentations-generate) * [POST /presentations/generate/:generationId/message](/post-send-followup-message) * [API scope and rate limits](/api-scope-rate-limits) # Quickstart Source: https://docs.chroniclehq.com/quickstart Get setup with Chronicle API fast Before you begin, you'll need: * Chronicle workspace with API enabled (available on Pro, Plus, and Max plans) * Node.js 18+ or another runtime with `fetch` support * Open [Chronicle](https://app.chroniclehq.com) * Navigate to Settings → API * Create an API key * Copy and store it securely, you won't be able to see it again ```shellscript theme={null} export CHRONICLE_API_KEY="your_api_key_here" ``` Include the key in the header with every request. Supported headers: * `Authorization: Bearer ` * `x-api-key: ` Skip this step if your prompt doesn't need source files. Otherwise, request a presigned upload target, post the file directly to S3, and keep the returned `download_url` for the next step. ```shellscript cURL theme={null} # 1. Request a presigned target TARGET=$(curl -s -X POST "https://api.chroniclehq.com/api/v1/uploads/create-target" \ -H "Authorization: Bearer $CHRONICLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_name": "research-notes.pdf", "content_type": "application/pdf", "declared_file_size": 524288 }') URL=$(echo "$TARGET" | jq -r '.url') DOWNLOAD_URL=$(echo "$TARGET" | jq -r '.download_url') FIELDS_ARGS=$(echo "$TARGET" | jq -r '.fields[] | "-F \(.name)=\(.value)"') # 2. POST the file to S3 (no Authorization header, file last) eval "curl -X POST \"$URL\" $FIELDS_ARGS -F file=@research-notes.pdf" ``` ```javascript JavaScript theme={null} async function uploadAttachment(file) { const target = await chronicleFetch("/uploads/create-target", { method: "POST", body: JSON.stringify({ file_name: file.name, content_type: file.type, declared_file_size: file.size, }), }); const form = new FormData(); for (const f of target.fields) form.append(f.name, f.value); form.append("file", file); // must be last const res = await fetch(target.url, { method: "POST", body: form }); if (!res.ok) throw new Error(`S3 upload failed: ${res.status}`); return { id: crypto.randomUUID(), url: target.download_url, file_name: file.name, type: file.type, }; } ``` Allowed formats: `.txt`, `.md`, `.pdf`, `.pptx`, and common image types. Max 50 MB per file. See [POST /uploads/create-target](/post-uploads-create-target) for the full reference. If you uploaded files in the previous step, pass them in the `attachments` array using the `download_url` you captured. ```shellscript cURL theme={null} curl -X POST "https://api.chroniclehq.com/api/v1/presentations/generate" \ -H "Authorization: Bearer $CHRONICLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_id": "tpl_123", "prompt": "Create a 5-slide presentation summarizing our Q2 product launch, customer traction, and next-quarter priorities.", "attachments": [ { "id": "att_123", "url": "'"$DOWNLOAD_URL"'", "file_name": "research-notes.pdf", "type": "application/pdf" } ] }' ``` ```javascript JavaScript theme={null} async function generatePresentation(templateId, prompt, attachments = []) { const data = await chronicleFetch("/presentations/generate", { method: "POST", body: JSON.stringify({ template_id: templateId, prompt, ...(attachments.length > 0 ? { attachments } : {}), }) }); return data; } const generationJob = await generatePresentation( "tpl_123", "Create a 5-slide presentation summarizing our Q2 product launch, customer traction, and next-quarter priorities." ); console.log(generationJob); ``` The request returns `202 Accepted` with a `generation_id` you can poll. ```json Response theme={null} { "generation_id": "gen_123", "status": "generating", "poll_url": "/api/v1/presentations/generate/gen_123/status" } ``` Poll the status endpoint until `status` is `completed`, `failed`, or `awaiting_input`. ```shellscript cURL theme={null} curl -X GET "https://api.chroniclehq.com/api/v1/presentations/generate/gen_123/status" \ -H "Authorization: Bearer $CHRONICLE_API_KEY" ``` ```javascript JavaScript theme={null} async function getGenerationStatus(generationId) { const data = await chronicleFetch( `/presentations/generate/${generationId}/status`, { method: "GET" } ); return data; } const status = await getGenerationStatus("gen_123"); console.log(status); ``` ```json Completed response theme={null} { "generation_id": "gen_123", "status": "completed", "presentation": { "id": "pres_789", "title": "Q2 Product Launch Review", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_789", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:00:00.000Z" } } ``` Open the `url` from the completed payload, or fetch the presentation later by ID: ```shellscript cURL theme={null} curl "https://api.chroniclehq.com/api/v1/presentations/pres_789" \ -H "Authorization: Bearer $CHRONICLE_API_KEY" ``` ```javascript JavaScript theme={null} const res = await fetch( "https://api.chroniclehq.com/api/v1/presentations/pres_789", { headers: { Authorization: `Bearer ${CHRONICLE_API_KEY}`, }, } ); console.log(await res.json()); ``` ```json Response theme={null} { "id": "pres_789", "title": "Q2 Product Launch Review", "workspace_id": "ws_123", "sections": [], "is_public": false, "url": "https://app.chroniclehq.com/ws_123/document/pres_789", "created_at": "2026-04-30T00:00:00.000Z", "updated_at": "2026-04-30T00:00:00.000Z" } ``` *** ## Related posts * [API reference](/api-reference) * [Authentication](/authentication) * [Error codes](/error-codes) # Troubleshooting Source: https://docs.chroniclehq.com/troubleshooting This guide covers the most common issues developers may hit when working with the Chronicle API and MCP integrations. If you're just getting started, first make sure you've: * created a valid API key * verified your request headers * tested a basic API call before trying generation or MCP flows *** ## Authentication errors HTTP status details for auth and other failures are summarized on [Error codes](/error-codes). ### 401 Unauthorized A `401 Unauthorized` response usually means the API key is missing, invalid, malformed, or no longer active. ### Checklist * Make sure you're sending an API key with the request. * Use one of these supported formats: * `Authorization: Bearer ` * `x-api-key: ` * Confirm the key was created in the correct Chronicle workspace. * Check whether the key has been revoked or rotated. * Make sure there are no extra spaces, quotes, or truncated characters. ### What to try next * Generate a new API key and retry the request. * Test with `curl` before debugging your app code. * Verify you're calling the intended environment and base URL. *** ## Rate limits and usage limits If requests start failing intermittently, taking longer than expected, or returning rate-limit-related errors, your client may be sending too many requests too quickly. See [API scope and rate limits](/api-scope-rate-limits) for official rate-limit behavior, retry guidance, and polling recommendations. ### What to do * Slow down request volume. * Avoid aggressive polling loops. * Add exponential backoff to retries. * Check usage-related fields in responses when available. ### Recommended polling behavior For long-running generation jobs: * wait 2-5 seconds between status checks * increase the delay gradually for longer jobs * stop polling after a reasonable timeout in your client ### Retry strategy Use exponential backoff with jitter for retriable failures. Example approach: * first retry after 2 seconds * second retry after 4 seconds * third retry after 8 seconds Avoid immediate repeated retries, which can make the issue worse. *** ## Generation job stays in progress If `POST /api/v1/presentations/generate` starts successfully but appears to stay in progress for too long, the job may still be running, may be stuck, or may have failed downstream. ### What to check * Make sure you're polling the status endpoint rather than expecting a synchronous final result. * Inspect the returned status carefully. * If the job remains in a generating state for several minutes, treat it as failed in your client and surface the returned failure reason when available. ### Recommended client behavior Your integration should: * create the generation job * poll status until completion or failure * handle timeouts gracefully * display useful progress or failure information to the user ### Best practices * Do not assume the first poll will return a final result. * Build for async behavior from the start. * Surface progress information when available. * Capture and log error codes, messages, and request identifiers. ### If the job seems stuck Collect the following before contacting support: * request timestamp * workspace identifier * generation or artifact identifier * prompt used * template identifier, if one was provided *** ## Generation completes but no usable presentation is returned If generation completes but the response does not include a usable Chronicle presentation, the issue is often in the conversion from generated output into a real editable presentation. ### Common symptoms * generation reports success, but no presentation URL is returned * output is incomplete or not usable by downstream clients * your client receives data that cannot be opened as a Chronicle presentation ### What to try * Retry the request with a simpler prompt. * Retry with a known-good template. * Confirm your integration is reading the final completed payload, not an intermediate response. * Verify that your client is using the returned presentation URL or identifier when present. ### How to handle this in docs and examples Your implementation should assume that generation is a multi-step workflow: 1. submit generation 2. poll for completion 3. read the completed presentation payload 4. open or store the returned presentation URL or identifier *** ## Template-created presentation appears blank or corrupted If a presentation created from a template appears empty, incomplete, or corrupted, the failure may be related to how document state was stored or reconstructed. ### What to check * Retry creation from the same template. * Try a different template to determine whether the issue is template-specific. * Confirm whether the issue reproduces consistently. * Test whether the issue is isolated to local or development environments. ### What to capture If you need help investigating, include: * template identifier * resulting presentation identifier * environment * timestamp of the request * whether the issue is reproducible across multiple templates ### Client guidance If your app receives a success response but the resulting presentation is unusable: * log the template id and created presentation id * show a recoverable error message to the user * allow the user to retry with a different template *** ## MCP setup issues If Claude Desktop or another MCP client does not detect Chronicle correctly, the problem is usually configuration, authentication, or connectivity. ### Common symptoms * the MCP server does not appear in the client * no tools are discovered * authentication loops or never completes * tools appear, but every tool call fails ### Checklist * Confirm the MCP configuration file is valid JSON. * Verify the configured command, path, or URL. * Restart the MCP client after making config changes. * Re-check environment variables, especially the API key. * Confirm the Chronicle API is reachable from the machine running the MCP client. ### Local stdio integrations For local MCP setups: * verify the server starts successfully from the command line * confirm the configured executable path is correct * make sure required environment variables are available to the MCP client process ### Hosted MCP integrations For hosted MCP setups: * verify the OAuth flow completed successfully * confirm the returned token is valid and scoped correctly * make sure your client can reach the MCP endpoint and authorization endpoints *** ## MCP connects, but tool calls fail If the MCP server is visible and tools are listed, but actual tool calls fail, the issue is often downstream authentication or network connectivity rather than MCP discovery itself. ### Start with a minimal happy path Test these in order: 1. list templates 2. create a presentation from a template 3. fetch that presentation If these succeed, the MCP transport is probably working and the issue is more likely isolated to generation or follow-up flows. ### What to verify * the API key works in a direct REST API request * the Chronicle API is reachable from the host machine * there are no firewall, proxy, or network restrictions * your client handles upstream failures gracefully ### Recommended fallback If an MCP tool call fails: * retry once with backoff * log the error returned by the client * test the equivalent REST API call directly * reduce the workflow to the smallest reproducible example *** ## Network errors and timeouts Network failures can happen anywhere in the flow: during authentication, generation, polling, or MCP tool execution. ### Best practices * set reasonable client-side timeouts * treat timeouts as retriable when safe * use idempotent retry behavior where possible * log full request context for failed calls ### Recommended logging For any failed request, log: * endpoint * method * timestamp * response status * request id, if available * workspace id * relevant resource ids such as template id or presentation id *** ## Before contacting support If you're still blocked, include as much of the following as possible: * the endpoint you called * the full HTTP status code * the response body * timestamp of the request * workspace identifier * API key creation time or whether the key was recently rotated * generation, template, or presentation identifiers involved * whether the issue is reproducible This makes it much faster to diagnose auth issues, generation failures, template problems, and MCP setup issues. *** ## Related posts * [Error codes](/error-codes) * [API scope and rate limits](/api-scope-rate-limits) * [Authentication](/authentication)