---
title: "Create a diagram"
method: POST
path: "/diagrams"
tags: ["Diagrams"]
---

# Create a diagram

`POST /diagrams`

## Request

- Base URL: `https://api.drawsql.app/v1`
- URL: `https://api.drawsql.app/v1/diagrams`
- Auth: HTTP bearer

Create a diagram in the token's team from SQL DDL or a schema patch.
Requires a token with the `write` ability.

Send exactly one of `ddl` or `schema`. A schema patch is applied inline,
so the response is `201` with the finished diagram. SQL DDL is parsed by
a background import: the response is `201` when that import finishes
within the request (`status` is `ready`, or `failed` if the SQL could
not be used), and `202` with `status: importing` when it does not.
In the `202` case, poll `GET /diagrams/{uuid}` every `poll_after_seconds`
until `status` is `ready` or `failed`; the schema endpoints return `409`
until then.

Retrying the same request (same token, name, driver, and payload)
returns the diagram the first attempt made, with `replayed: true`,
rather than creating a second one: `200` once it is `ready`, `202`
while its import is still running. The window is the running import,
plus two minutes after it finishes. A `failed` create is never
returned, so a retry makes a fresh attempt.

Canvas positions and sizes (`left`, `top`, `width`, `height`) are not
accepted here — the layout is computed. Rate limit: 10 requests/minute
per token.

## Request body

- CreateDiagramRequest: The body of `POST /diagrams`. Exactly one of `ddl` or `schema` is required.
  - `ddl` string: SQL DDL (`CREATE TABLE` / `ALTER TABLE` statements). Max 200KB.
  - `schema` union: A schema patch, as an object or as a JSON string. Max 500KB. The format is defined by `GET /schema-patch-contract`. Canvas positions and sizes are rejected here.
    - object
    - string
  - `name` string: The diagram's name. Defaults to "Untitled".
  - `driver` 'mysql' | 'pgsql' | 'sqlsrv': The database dialect the schema is written in.

## Response `200`

A retry of a request that already created a diagram, which is now `ready`. The body is the existing diagram, with `replayed: true`.

- CreateDiagramResponse
  - `data` Diagram, required: A diagram's public metadata. Never exposes internal ids or FKs.
    - `uuid` string, uuid, required
    - `name` string, required
    - `slug` string, required
    - `description` string, nullable, required
    - `team` object, required
      - `name` string, required
      - `slug` string, required
    - `table_count` integer, nullable, required: Null while `status` is `importing` — the tables are not known yet.
    - `url` string, uri, required: Absolute canonical URL of the diagram.
    - `embed_url` string, uri, required: Absolute embeddable URL of the diagram.
    - `updated_at` string, date-time, nullable, required: ISO 8601 timestamp of the last update.
    - `status` 'ready' | 'importing' | 'failed', required: `ready` for every diagram made in the app. Diagrams created from SQL DDL through the API are `importing` until the import finishes.
    - `error` object, nullable, required: Why the import failed. Null unless `status` is `failed`.
      - `code` string, required
      - `message` string, required
  - `counts` object, nullable, required: How much was created, once the diagram is ready. Null while the import is running or after it failed.
    - `tables` integer
    - `columns` integer
    - `indexes` integer
    - `relationships` integer
    - `groups` integer
    - `sticky_notes` integer
  - `warnings` string[], required: Non-fatal notes about the submitted patch, such as fields that were dropped because they are not part of the documented format.
  - `replayed` boolean, required: Whether this response returns a diagram an earlier identical request created.
  - `poll_after_seconds` integer, required: How long to wait between polls of `GET /diagrams/{uuid}` while importing.

## Other responses

- `201`: The diagram was created and its schema is in place.
- `202`: The diagram's import is still running, for a new diagram or for a retry of one whose import has not finished (`replayed: true`). Poll `GET /diagrams/{uuid}` until `status` is `ready` or `failed`.
- `400`: The request body is not a JSON object.
- `401`: Missing, malformed, expired, or revoked token (code `unauthenticated`), or a valid token whose team no longer exists or whose user is no longer a member of that team (code `invalid_token`).
- `403`: The token lacks the `write` ability (code `insufficient_scope`), its team is not on a paid plan (code `plan_required`), or it may not create diagrams in that team (code `forbidden`).
- `413`: The `ddl` (200KB) or `schema` (500KB) payload is too large.
- `422`: The request was understood but refused: `invalid_request` (neither or both of `ddl`/`schema`, or a bad `name`/`driver`), `invalid_schema` (the patch failed validation; `errors` lists up to 15 of them), `no_tables` (the patch defines no tables), `unsupported_field` (`paths` lists the positions and sizes to remove), or `plan_limit` (`limit_type` is `tables`, with `limit` and `requested`, or `private_diagrams`: every API-created diagram is private, and a team at that cap is refused rather than given a public diagram).
- `429`: Too many requests.

---

[API](https://skmtc.dev/drawsql/apis/drawsql-api.md) - [All operations](https://skmtc.dev/drawsql/apis/drawsql-api/llms.txt) - [OpenAPI document](https://skmtc.dev/drawsql/apis/drawsql-api/revisions/8b333e0bae67?raw)
