# SciFig API and MCP server

Create scientific figures, charts and flowcharts from code or from AI assistants. Human-readable version: https://scifig.ai/developers. OpenAPI: https://scifig.ai/api/v1/openapi.json.

## Overview

The SciFig API creates scientific figures, charts and flowcharts with the same engines, prices and credits as the website. The base URL is `https://scifig.ai/api/v1`. Every creation is a job: you create it, then read its status and outputs.

AI assistants can use SciFig through the remote MCP server at `https://mcp.scifig.ai/mcp` (see "MCP server" below).

## Authentication

Send `Authorization: Bearer <credential>` on every request except `/pricing`, `/openapi.json` and `/openapi.yaml`.

- Keys with any write scope require an active subscription or a positive credit-pack balance and expire after 30, 90 or 365 days. Read-only keys may also have no expiry.
- OAuth access tokens (`sf_at_…`): issued to MCP clients after you approve them, valid for 1 hour. Clients that ask for `offline_access` also get a refresh token (`sf_rt_…`) valid for 30 days. Revoke an app under Settings → API Keys → Connected apps.
- Choose any of eight independent scopes. None grants another scope; uploads, job reads and balance reads require their own permissions. `images:generate`, `images:edit`, `images:convert`, `charts:generate`, `flowcharts:generate`, `files:write`, `jobs:read`, `credits:read`
- `GET /api/v1/capabilities` requires a valid credential, but no business scope. It returns only `granted_scopes`, never balance, plan or account data.
- Every authenticated call, including permission denials and other errors, is recorded as metadata for 30 days. No prompts, content, file names, IP addresses or credentials are stored. Logging is best effort during platform failures. Settings shows keys, request history and connected apps in separate lists of 10 per page. Request credits describe the associated job’s settlement; do not sum replay rows as spending.
- Only allowed `CIMD` clients may use `private_key_jwt` with verified public keys and replay protection; anonymous dynamic registration still accepts only `none` or `client_secret_basic`.

## Quick start

```bash
curl https://scifig.ai/api/v1/images/generations \
  -H "Authorization: Bearer $SCIFIG_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Prefer: wait=25" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Mitochondrial electron transport chain, labelled", "aspect_ratio": "4:3"}'
```

The response is the job. When it is not finished yet the status is `202` with `Location: /api/v1/jobs/{id}`; poll that URL every few seconds until `status` is `succeeded`, `failed` or `canceled`, then download `outputs[].download_url`.

## Conventions

- Requests and responses are JSON; uploads and chart data are `multipart/form-data`. Field names are `snake_case`.
- Every `POST` that creates something requires an `Idempotency-Key` header (1–255 visible ASCII characters). Retrying with the same key and the same body returns the same job and never charges twice; the same key with a different body returns `409 IDEMPOTENCY_CONFLICT`. Two requests with the same key at the same time return the same job, or `409 IDEMPOTENCY_IN_PROGRESS` for the one that arrives while the other is being admitted. A request that was refused (for example `INSUFFICIENT_CREDITS`) keeps that answer for its key: after topping up or upgrading, send it with a new key. Once the job exists, a retryable error (`429`, `503`, `409 IDEMPOTENCY_IN_PROGRESS`) or `500 INTERNAL_ERROR` carries `details.job_id`: retry with the same key or poll `GET /jobs/{job_id}`, not with a new key. A job that was accepted but could not be read back is answered with `202` and status `queued`.
- `Prefer: wait=N` (1–30 seconds) holds the response until the job finishes or the time is up.
- Every response carries `X-Request-Id`. Errors use `{"error": {"code", "message", "request_id", "details"}}`.
- The API is not available to browsers through CORS; call it from a server or a script.

## Endpoints and credits

| Endpoint | Purpose | Credits |
| --- | --- | --- |
| POST /images/generations | Generate a scientific illustration | 50 / 75 / 100 (standard / advanced / premium) |
| POST /images/edits | Edit an image: by instruction (a mask redraws one region), white background, aspect ratio, recolor or text replacement | 50 / 75 / 100; with mask or `replace_text` 100 |
| POST /images/text-detections | Find the text in an image before replacing it | 0 |
| POST /images/upscales | Upscale to 2K, 4K or 8K | 50 / 100 / 150 |
| POST /images/conversions | Convert to SVG, PNG, PDF, TIFF or PPTX (flowchart SVGs to PNG, PDF, TIFF or PPTX) | SVG 150; others free (200 per day) |
| POST /charts/generations | Plot a chart from CSV, TSV or Excel data | 100 |
| POST /charts/edits | Change the latest version of a chart | 100 |
| POST /flowcharts/generations | Draw a flowchart as SVG | 100 |
| POST /flowcharts/edits | Change the latest version of a flowchart | 100 |
| POST /files | Upload an image, a mask or a flowchart attachment | 0 |
| GET /jobs/{id} | Status, credits and download links of a job | 0 |
| GET /jobs | Your recent jobs, newest first (cursor pagination) | 0 |
| GET /files/{file_id} | Download a file | 0 |
| GET /credits | Balance, plan and task limits | 0 |
| GET /pricing | Credit price of every operation (no authentication) | 0 |
| GET /openapi.json | OpenAPI 3.1 description (no authentication) | 0 |
| GET /openapi.yaml | The same description as YAML | 0 |

## Request fields

`POST /images/generations`: `prompt` (required), `aspect_ratio` (`1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`), `quality` (`standard`, `advanced`, `premium`), `style`, `palette` (a preset id or 1–4 `#RRGGBB` colours), `reference_file_ids` (up to 3). Some models and reference images need a paid plan; the error says so.

`POST /images/edits`: `image` (a `file_id`) and `operation`. `instruct` (the default) takes `instruction` (up to 2000 characters) and an optional `mask` (PNG, white marks the area to change; a mask always uses the `premium` model). `white_background` replaces the background with solid white. `aspect_ratio` reframes the image to `aspect_ratio`. `recolor` applies `palette` (1–8 `#RRGGBB` colours) or picks a palette. All of these take `quality`. `replace_text` takes `text_detection` (the id from `POST /images/text-detections` on the same image) and `replacements` (`[{"index", "text"}]`), and always uses the `premium` text-editing model.

`POST /images/text-detections`: `image`. Returns `id`, the image `width` and `height`, and `texts` (`index`, `text`, `corners`) in reading order. Free; it shares the website's limit of 20 detections per 15 minutes.

`POST /images/upscales`: `image`, `scale` (`2k`; `4k` needs the Starter plan or higher, `8k` the Plus plan or higher; a credit-pack balance above zero counts as Pro).

`POST /images/conversions`: `image`, `format` (`svg`, `png`, `pdf`, `tiff`, `pptx`). For SVG, `vector_mode` is `editable_text` (default) or `full_vector`; the source is a PNG, JPEG or WebP image. For the other formats, `dpi` is 300, 600 or 1000; DPI is written as metadata and sets the page size, pixels are not resampled (upscale first for more pixels). Their source is a PNG or JPEG image or a flowchart's SVG output; WebP is not accepted, and PNG and TIFF need a PNG source. A flowchart's SVG output is rendered on the server with Arial and Noto fonts, `width` (256–4096, default 2048) sets the long edge in pixels, and text the fonts cannot draw is refused. The PPTX holds the rendered image, not editable shapes.

`POST /charts/generations` (`multipart`): `data` (CSV, TSV, TXT, XLSX or XLS, up to 8 MB; the delimiter of text files is detected), `request` (what to plot, 1–16,000 characters), optional `chart_types` (comma-separated), `journal_style` (`default`, `nature`, `science`, `cell`, `plos`, `acs`), `figure_width` (`single`, `onehalf`, `double`), `color_palette`, `sheet_name` (the worksheet of a workbook with several sheets). Data that cannot be read returns `400 INVALID_REQUEST` with `details.field = data` and the parser's code in `details.reason`. Outputs PNG, PDF, TIFF and EPS.

`POST /charts/edits` and `POST /flowcharts/edits`: `job` (an earlier chart or flowchart job) and `instruction` (1–16,000 characters for charts, 1–8,000 for flowcharts). The edit continues from that job's output, keeping the data, chart types and journal style (charts) or the aspect ratio (flowcharts). Only the latest version can be edited, as in the workspace: if a newer version exists (also one made on the website), the answer is `409 REVISION_CONFLICT` with `details.reason = NOT_LATEST_REVISION`. While the parent job is still running the answer is a retryable `409 REVISION_CONFLICT` (`PARENT_JOB_RUNNING`, with `Retry-After`); a parent that failed returns `400 INVALID_REQUEST` (`JOB_NOT_SUCCEEDED`).

`POST /flowcharts/generations`: `prompt` (1–8,000 characters), `aspect_ratio` (`auto` or one of the ratios above), optional `attachment_file_ids` (up to 8, uploaded with purpose `flowchart_attachment`). Outputs SVG.

## Jobs

`status` is `queued`, `processing`, `succeeded`, `failed` or `canceled`. `credits` reports `reserved`, `charged` and `refunded`, read from the ledger. A failed job is refunded.

A generation or edit that timed out is `failed` with `error.code = TASK_TIMED_OUT` and a `late_delivery_until` timestamp: until then it may still be delivered for free, so keep reading it instead of creating a new job.

A content-policy refusal is either `422 CONTENT_REJECTED` when the request is created, or a `failed` job with `error.code = CONTENT_REJECTED` when it happens while the task runs. Both are refunded.

Each output has a `file_id`, a `format` and a `download_url` that works without credentials for one hour. PNG outputs also report `width` and `height`. Chart and flowchart outputs carry an `editor_url` that opens the result in its website workspace; illustrations and conversions have none.

Image outputs (PNG, JPEG, WebP) can be passed by `file_id` as `image` or reference to the image endpoints. Conversions take sources up to 20 MB; to PNG, PDF, TIFF and PPTX they take PNG or JPEG images and a flowchart's SVG output, not WebP.

## Files

`POST /files` (`multipart`): `file` and `purpose` (`image`, `mask` or `flowchart_attachment`). Images are PNG, JPEG or WebP up to 20 MB; masks are PNG up to 8 MB; flowchart attachments follow the FlowChart workspace formats up to 32 MB. The response is `201` with `file_id`, `purpose`, `content_type`, `size_bytes`, `expires_at` and `created_at`. Uploaded files can be used for 24 hours. Uploads share the website’s upload limits; beyond them the answer is `429 RATE_LIMITED`. An optional `Idempotency-Key` (1–255 visible ASCII characters) makes the upload repeatable: the same account, key and `purpose` return the first file with `201`, without uploading again or using the upload limits; the same key and `purpose` with different bytes return `409 IDEMPOTENCY_CONFLICT`. Replaying an expired upload returns `400 INVALID_REQUEST` with `details.reason = FILE_EXPIRED`; upload it again with a new key.

`GET /files/{file_id}` downloads your own file with your credential, or with the signed `download_url` from a job. A signed link that has expired or was altered returns `404`, and so does every link of an account that is banned or being deleted.

## Errors

| Code | HTTP | Meaning |
| --- | --- | --- |
| UNAUTHORIZED | 401 | Missing or invalid credentials. |
| ACCOUNT_UNAVAILABLE | 401 | This account cannot use the API. |
| SCOPE_DENIED | 403 | The credential does not grant this permission. |
| INVALID_REQUEST | 400 | The request is invalid. |
| IDEMPOTENCY_KEY_REQUIRED | 400 | An Idempotency-Key header (1-255 visible ASCII characters) is required. |
| IDEMPOTENCY_CONFLICT | 409 | This Idempotency-Key was already used with a different request. |
| IDEMPOTENCY_IN_PROGRESS | 409 | A request with this Idempotency-Key is still being processed. |
| NOT_FOUND | 404 | Not found. |
| REVISION_CONFLICT | 409 | The chart or flowchart job cannot be edited now: `NOT_LATEST_REVISION`, `EDIT_IN_PROGRESS`, or `PARENT_JOB_RUNNING` while the parent job is still running. |
| PAYLOAD_TOO_LARGE | 413 | The request body is too large. |
| INSUFFICIENT_CREDITS | 402 | Not enough credits. |
| CONTENT_REJECTED | 422 | The content policy refused the request. Returned as `422` when the request is created, or as `error.code` of a failed job when it happens while the task runs; refunded either way. |
| PLAN_REQUIRED | 403 | Your plan does not include this option. |
| RATE_LIMITED | 429 | Too many requests, tasks per hour, conversions per day or uploads; `details.reason` says which. |
| CONCURRENCY_LIMITED | 429 | Too many tasks are running for this account. |
| SERVICE_UNAVAILABLE | 503 | The service is temporarily unavailable. Retry with the same Idempotency-Key. |
| TASK_FAILED | 502 | The task failed and its credits were refunded. Send a new request (with a new Idempotency-Key) to try again. |
| INTERNAL_ERROR | 500 | Internal error. |

`details.reason` names the exact cause of `INVALID_REQUEST`, `PLAN_REQUIRED` and similar errors. Only `INSUFFICIENT_CREDITS` includes `details.top_up_url`; its `utm_source` is `api` or `mcp`, after the channel the request came through.

Codes that appear only inside a failed job:

| error.code | Meaning | What to do |
| --- | --- | --- |
| TASK_TIMED_OUT | The task timed out and was refunded. An illustration generation or edit may carry `late_delivery_until`: until then the result can still arrive for free. | Keep reading the job until `late_delivery_until`; otherwise send a new request with a new key |
| CONTENT_REJECTED | The content policy refused the result while the task ran; refunded. The same code is returned as `422` when the request itself is refused at creation. | Change the prompt or the input and use a new key |
| ADMISSION_INCOMPLETE | The job was never started because a retryable error stopped it. No credits were used. | Send the same request with the same Idempotency-Key |
| TASK_NOT_FOUND | The job was admitted but its task can no longer be found. | Send a new request with a new key |
| TASK_FAILED | The task failed and was refunded. Every engine failure without a public code of its own becomes `TASK_FAILED`; the engine code is in `details.reason`. | Send a new request with a new key |
| INTERNAL_ERROR | The task failed without reporting a reason; refunded. | Send a new request with a new key |

## Rate limits

- Creating: 20 requests per minute per credential and 40 per minute per account. An OAuth credential is counted per client and account.
- Reading: 120 requests per minute per credential.
- Running tasks follow your plan; at most 200 tasks start per hour. Both are shared with the website.
- Responses that pass authentication and the scope check carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a `429` carries `Retry-After`.
- `429 RATE_LIMITED` has several causes; read `details.reason` and the [rate limits page](/docs/api/rate-limits).

## Install the official SciFig plugin

Add the SciFig marketplace in Claude Code to install the remote MCP connection and four workflow skills for setup, figures, charts and export.

```text
/plugin marketplace add lilingm963/scifig-plugins
/plugin install scifig@scifig-plugins
```

- [API & MCP overview](/developers)
- [MCP installation guide](/developers/mcp/install.md)
- [API reference](/docs/api)
- [Explore the official plugin](https://github.com/lilingm963/scifig-plugins)
- [MCP Registry record](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.lilingm963%2Fscifig/versions/1.0.0)

## MCP server

Server URL: `https://mcp.scifig.ai/mcp` (Streamable HTTP). Clients that support OAuth connect by signing in to SciFig and approving access; clients that send headers can use an API key as `Authorization: Bearer sk_live_…`.

| Tool | What it does |
| --- | --- |
| generate_figure | Create a scientific illustration from a prompt |
| edit_figure | Edit an image: instruction (optionally a masked region), white background, aspect ratio, recolor or text replacement |
| detect_text | Find the text in an image before replacing it |
| upscale_figure | Upscale to 2K, 4K or 8K |
| convert_figure | Convert to SVG, PNG, PDF, TIFF or PPTX |
| generate_chart | Plot a chart from CSV or TSV text or JSON rows (up to 1 MB) |
| edit_chart | Change the latest version of a chart |
| generate_flowchart | Draw a flowchart as SVG, optionally from up to 8 attachments |
| edit_flowchart | Change the latest version of a flowchart |
| get_job | Status, credits and download links of a job |
| list_figures | Recent jobs |
| get_credit_balance | Credit balance, plan and limits |

Creation tools wait up to 25 seconds. If a job is still running, call `get_job` later; calling the creation tool again creates and charges a new job. Results list each download link and, for charts and flowcharts, the link that opens the result in the SciFig editor.

Initialization uses `/capabilities` and does not require `credits:read`. Each tool requires its own scope. External URL/data URL uploads additionally require `files:write`, checked before fetching. Reusing a `file_id` does not. Inline downloads and `get_job` need `jobs:read`; without it, preserve the job result and explain the limitation rather than requesting broader access without consent.

## Connect your client

Claude (claude.ai and Claude Desktop): Settings → Connectors → Add custom connector, enter the server URL, then sign in to SciFig and allow access.

Open ChatGPT `Plugins` and add a custom private MCP connection. Available menus depend on your account.

Claude Code:

```bash
claude mcp add --transport http scifig https://mcp.scifig.ai/mcp
```

Codex (`~/.codex/config.toml`):

```toml
[mcp_servers.scifig]
url = "https://mcp.scifig.ai/mcp"
```

Open Cursor `Customize` → MCP, then edit `mcp.json` without overwriting existing servers. Then select `Authenticate` and approve SciFig in your browser.

```json
{
  "mcpServers": {
    "scifig": {
      "url": "https://mcp.scifig.ai/mcp"
    }
  }
}
```

Clients without OAuth can add the header `Authorization: Bearer sk_live_…` with an API key from Settings → API Keys.
