openapi: 3.1.0
info:
  title: DrawSQL API
  version: "1.0.0"
  summary: Access to your DrawSQL diagrams and schemas.
  description: |
    The DrawSQL API is a REST API for programmatic access to the
    diagrams in one of your teams. Authenticate with a personal access token
    (create one under Account → API tokens); every token is scoped to a single
    team and can only read the diagrams you can see in that team.

    Successful JSON resource responses are wrapped in a `data` envelope. Error
    responses use an `error` envelope, and `304 Not Modified` has no body. The
    field sets are a frozen contract: additive changes ship without a version
    bump, while breaking changes ship as `/v2` with at least six months' notice.
  contact:
    name: DrawSQL support
    url: https://drawsql.app/contact
  license:
    name: Proprietary
    url: https://drawsql.app/terms

servers:
  - url: https://api.drawsql.app/v1
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Diagrams
    description: List and read diagrams and their schemas.

paths:
  /diagrams:
    get:
      tags: [Diagrams]
      operationId: listDiagrams
      summary: List diagrams
      description: |
        Cursor-paginated list of the diagrams visible to the token, newest
        activity first. Rate limit: 60 requests/minute per token.
      parameters:
        - name: limit
          in: query
          required: false
          description: Page size (1–100).
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          required: false
          description: Opaque cursor from a previous response's `meta.next_cursor`.
          schema:
            type: string
      responses:
        "200":
          description: A page of diagrams.
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Diagram"
                  meta:
                    $ref: "#/components/schemas/CursorMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

  /diagrams/{uuid}:
    get:
      tags: [Diagrams]
      operationId: getDiagram
      summary: Get a diagram
      description: |
        Metadata for a single diagram. Unknown, malformed, and unauthorized
        UUIDs all return an identical `404` (no enumeration signal).
        Rate limit: 60 requests/minute per token.
      parameters:
        - $ref: "#/components/parameters/DiagramUuid"
      responses:
        "200":
          description: The diagram.
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Diagram"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /diagrams/{uuid}/schema:
    get:
      tags: [Diagrams]
      operationId: getDiagramSchema
      summary: Get a diagram's schema
      description: |
        A diagram's schema in the curated, "schema-pure" profile: `driver`,
        `schema_version`, `tables` (each carrying its `columns` and `indexes`
        nested inline, in schema order), and `relationships`. Layout, groups, and
        sticky notes are omitted. For the verbatim, re-importable document use
        `/schema-full` (this response is a strict subset of it: same nesting,
        same field names).

        Supports ETag conditional requests. The ETag is a hash of this payload,
        so a canvas-only edit (moving a table, recoloring) still returns
        `304 Not Modified`. Only `application/json` is available in v1; any other
        `Accept` (or a `?format=` other than `json`) returns `406`.
        Rate limit: 30 requests/minute per token (serialization is heavier here).
      parameters:
        - $ref: "#/components/parameters/DiagramUuid"
        - name: If-None-Match
          in: header
          required: false
          description: An ETag from a previous response. Returns `304` if unchanged.
          schema:
            type: string
      responses:
        "200":
          description: The schema.
          headers:
            ETag:
              description: Opaque validator for the current schema content.
              schema:
                type: string
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Schema"
        "304":
          description: The schema is unchanged since the supplied `If-None-Match`.
          headers:
            ETag:
              description: Opaque validator for the current schema content.
              schema:
                type: string
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "406":
          description: A non-JSON representation was requested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: not_acceptable
                  message: Only application/json is available on this endpoint for now.
        "429":
          $ref: "#/components/responses/RateLimited"

  /diagrams/{uuid}/schema-full:
    get:
      tags: [Diagrams]
      operationId: getDiagramSchemaFull
      summary: Get a diagram's full schema document
      description: |
        The verbatim, re-importable DrawSQL JSON document for a diagram: the same
        denormalized shape as `/schema`, plus layout (positions, colors, sizes),
        `groups`, and `sticky_notes`. This is the native canvas format, minus only
        security-sensitive persistence fields.

        The curated `/schema` profile is a strict subset of this document.
        Supports ETag conditional requests. Here the ETag tracks the schema
        revision, so ANY edit (including a canvas-only layout change) yields a new
        ETag. Rate limit: 30 requests/minute per token.
      parameters:
        - $ref: "#/components/parameters/DiagramUuid"
        - name: If-None-Match
          in: header
          required: false
          description: An ETag from a previous response. Returns `304` if unchanged.
          schema:
            type: string
      responses:
        "200":
          description: The full schema document.
          headers:
            ETag:
              description: Opaque validator for the current schema revision.
              schema:
                type: string
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/SchemaFull"
        "304":
          description: The document is unchanged since the supplied `If-None-Match`.
          headers:
            ETag:
              description: Opaque validator for the current schema revision.
              schema:
                type: string
            X-RateLimit-Limit:
              $ref: "#/components/headers/RateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/RateLimitRemaining"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        A DrawSQL personal access token, sent as `Authorization: Bearer <token>`.
        Tokens are team-scoped. Session cookies are never accepted.

  parameters:
    DiagramUuid:
      name: uuid
      in: path
      required: true
      description: The diagram's UUID.
      schema:
        type: string
        format: uuid
        example: 123e4567-e89b-12d3-a456-426614174000

  headers:
    RateLimitLimit:
      description: Requests permitted in the current window.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer

  responses:
    Unauthorized:
      description: >-
        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`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: unauthenticated
              message: A valid personal access token is required. Create one at https://drawsql.app/user/api-tokens.
    Forbidden:
      description: >-
        The token's team is not on a paid plan (code `plan_required`), or the
        token is not allowed to perform this action (code `forbidden`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: plan_required
              message: The public API requires a paid plan. Upgrade this token's team to restore access.
    NotFound:
      description: No such diagram, or it is not visible to this token.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: not_found
              message: Not found.
    InvalidRequest:
      description: A query parameter failed validation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: invalid_request
              message: The limit field must not be greater than 100.
    RateLimited:
      description: Too many requests.
      headers:
        Retry-After:
          $ref: "#/components/headers/RetryAfter"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: rate_limited
              message: Too many requests.

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: A stable, machine-readable error code.
              examples: [unauthenticated, invalid_token, forbidden, plan_required, not_found, invalid_request, not_acceptable, rate_limited, request_failed]
            message:
              type: string
              description: A human-readable explanation.

    CursorMeta:
      type: object
      required: [next_cursor, has_more]
      properties:
        next_cursor:
          type: [string, "null"]
          description: Cursor for the next page, or `null` on the last page.
        has_more:
          type: boolean
          description: Whether another page is available.

    Diagram:
      type: object
      description: A diagram's public metadata. Never exposes internal ids or FKs.
      required: [uuid, name, slug, description, team, table_count, url, embed_url, updated_at]
      properties:
        uuid:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: [string, "null"]
        team:
          type: object
          required: [name, slug]
          properties:
            name:
              type: string
            slug:
              type: string
        table_count:
          type: integer
        url:
          type: string
          format: uri
          description: Absolute canonical URL of the diagram.
        embed_url:
          type: string
          format: uri
          description: Absolute embeddable URL of the diagram.
        updated_at:
          type: [string, "null"]
          format: date-time
          description: ISO 8601 timestamp of the last update.

    Schema:
      type: object
      description: |
        A diagram's schema in the curated "schema-pure" profile. Denormalized:
        each table carries its `columns` and `indexes` inline. A strict subset of
        `SchemaFull` (same field names, no renames).
      required: [driver, schema_version, tables, relationships]
      properties:
        driver:
          type: [string, "null"]
          description: Database dialect.
          examples: [mysql, pgsql, sqlsrv]
        schema_version:
          type: number
          description: The document-format version carried in the payload (distinct from the `/v1` API version).
        tables:
          type: array
          description: Tables, sorted by name for diff stability.
          items:
            $ref: "#/components/schemas/Table"
        relationships:
          type: array
          items:
            $ref: "#/components/schemas/Relationship"

    Table:
      type: object
      description: A table with its columns and indexes nested inline.
      required: [uuid, name, comment, columns, indexes]
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        comment: { type: [string, "null"] }
        columns:
          type: array
          description: Columns in schema (DDL) order; array position is semantic.
          items:
            $ref: "#/components/schemas/Column"
        indexes:
          type: array
          items:
            $ref: "#/components/schemas/Index"

    Column:
      type: object
      description: |
        A column in the schema-pure profile. The type is given as a resolvable
        name (`driver_data_type_name`); the internal `driver_data_type_id` is not
        exposed here (see `ColumnFull`). Optional fields appear only when set.
      required:
        - uuid
        - name
        - driver_data_type_name
        - driver_data_type_params
        - default
        - is_nullable
        - is_primary_key
        - is_unique_key
        - is_index
        - is_auto_increment
        - is_unsigned
        - comment
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        driver_data_type_name:
          type: string
          description: The column's data type name within the schema's driver (e.g. "varchar", "int").
        driver_data_type_params:
          type: [array, "null"]
          description: Type parameters, e.g. length or precision.
          items: {}
        default:
          type: [string, number, "null"]
          description: Native number when the column's default is numeric, otherwise a string.
        default_type:
          type: string
          description: Present only when the default is typed (e.g. "number", "expression").
        is_nullable: { type: boolean }
        is_primary_key: { type: boolean }
        is_unique_key: { type: boolean }
        is_index: { type: boolean }
        is_auto_increment: { type: boolean }
        is_unsigned: { type: boolean }
        enum_values:
          type: array
          description: Present only for enum columns.
          items: { type: string }
        set_values:
          type: array
          description: Present only for MySQL SET columns.
          items: { type: string }
        array_suffix:
          type: string
          description: "PostgreSQL array suffix, e.g. '[]' or '[5]'. Present only for array columns."
        identity_params:
          type: object
          description: "SQL Server identity parameters { seed, increment }. Present only for identity columns."
        comment: { type: [string, "null"] }

    Index:
      type: object
      description: An index with its covered columns hydrated to { uuid, name } refs.
      required: [uuid, name, type, columns]
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        type:
          type: string
          description: Index type.
          examples: [index, unique, primary]
        columns:
          type: array
          description: The columns covered by this index, in order.
          items:
            $ref: "#/components/schemas/IndexColumnRef"

    IndexColumnRef:
      type: object
      description: A reference to a column covered by an index.
      required: [uuid, name]
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }

    Relationship:
      type: object
      required: [uuid, source_table_uuid, source_column_uuid, target_table_uuid, target_column_uuid, type]
      properties:
        uuid: { type: string, format: uuid }
        source_table_uuid: { type: string, format: uuid }
        source_column_uuid: { type: string, format: uuid }
        target_table_uuid: { type: string, format: uuid }
        target_column_uuid: { type: string, format: uuid }
        type:
          type: string
          description: Cardinality of the relationship.
          examples: [one-to-one, one-to-many, many-to-one]
        name:
          type: string
          description: Present only when the relationship is named.

    SchemaFull:
      type: object
      description: |
        The verbatim, re-importable DrawSQL JSON document: the schema-pure shape
        plus layout, `groups`, and `sticky_notes`. Security-sensitive persistence
        fields are stripped. `Schema` is a strict subset of this.
      required: [driver, schema_version, tables, relationships, groups, sticky_notes]
      properties:
        driver:
          type: [string, "null"]
          examples: [mysql, pgsql, sqlsrv]
        schema_version:
          type: number
        tables:
          type: array
          items:
            $ref: "#/components/schemas/TableFull"
        relationships:
          type: array
          items:
            $ref: "#/components/schemas/RelationshipFull"
        groups:
          type: array
          items:
            $ref: "#/components/schemas/Group"
        sticky_notes:
          type: array
          items:
            $ref: "#/components/schemas/StickyNote"

    TableFull:
      type: object
      description: 'A table in the full document: the schema fields plus layout.'
      required: [uuid, name, comment, color, order, width, left, top, parent_uuid, record_type, columns, indexes]
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        comment: { type: [string, "null"] }
        color: { type: string }
        order: { type: string, description: Fractional order key. }
        width: { type: [integer, "null"] }
        left: { type: integer }
        top: { type: integer }
        parent_uuid:
          type: [string, "null"]
          description: UUID of the group this table belongs to, if any.
        record_type: { type: string, examples: [table] }
        columns:
          type: array
          items:
            $ref: "#/components/schemas/ColumnFull"
        indexes:
          type: array
          items:
            $ref: "#/components/schemas/IndexFull"

    ColumnFull:
      type: object
      description: |
        A column in the full document: every `Column` field, plus `table_uuid`
        and `order`. The type is identified by `driver_data_type_name`; the
        internal `driver_data_type_id` is NOT present; it was dropped from the
        format, so re-import maps the name back.
      required:
        - uuid
        - table_uuid
        - name
        - driver_data_type_name
        - driver_data_type_params
        - default
        - order
        - is_nullable
        - is_primary_key
        - is_unique_key
        - is_index
        - is_auto_increment
        - is_unsigned
        - comment
      properties:
        uuid: { type: string, format: uuid }
        table_uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        driver_data_type_name:
          type: string
          description: The type identifier (e.g. `bigint`). Import maps it back to a driver type.
        driver_data_type_params:
          type: [array, "null"]
          items: {}
        default: { type: [string, number, "null"] }
        default_type: { type: string }
        order: { type: string }
        is_nullable: { type: boolean }
        is_primary_key: { type: boolean }
        is_unique_key: { type: boolean }
        is_index: { type: boolean }
        is_auto_increment: { type: boolean }
        is_unsigned: { type: boolean }
        enum_values:
          type: array
          items: { type: string }
        set_values:
          type: array
          items: { type: string }
        array_suffix: { type: string }
        identity_params: { type: object }
        comment: { type: [string, "null"] }

    IndexFull:
      type: object
      description: 'An index in the full document: schema fields plus the raw column_uuids.'
      required: [uuid, table_uuid, name, type, order, column_uuids, columns]
      properties:
        uuid: { type: string, format: uuid }
        table_uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        type:
          type: string
          examples: [index, unique, primary]
        order: { type: string }
        column_uuids:
          type: array
          description: UUIDs of the columns covered by this index, in order.
          items: { type: string, format: uuid }
        columns:
          type: array
          items:
            $ref: "#/components/schemas/IndexColumnRef"

    RelationshipFull:
      type: object
      required: [uuid, source_table_uuid, source_column_uuid, target_table_uuid, target_column_uuid, order, type, record_type]
      properties:
        uuid: { type: string, format: uuid }
        source_column_uuid: { type: string, format: uuid }
        source_table_uuid: { type: string, format: uuid }
        target_column_uuid: { type: string, format: uuid }
        target_table_uuid: { type: string, format: uuid }
        order: { type: [string, "null"] }
        type:
          type: string
          examples: [one-to-one, one-to-many, many-to-one]
        name: { type: string }
        record_type: { type: string, examples: [relationship] }

    Group:
      type: object
      description: A canvas group. Present only in the full document.
      required: [uuid, name, color, order, width, height, left, top, comment, is_collapsed, record_type]
      properties:
        uuid: { type: string, format: uuid }
        name: { type: [string, "null"] }
        color: { type: string }
        order: { type: string }
        width: { type: integer }
        height: { type: integer }
        left: { type: integer }
        top: { type: integer }
        comment: { type: [string, "null"] }
        is_collapsed: { type: [boolean, "null"] }
        record_type: { type: string, examples: [group] }

    StickyNote:
      type: object
      description: A canvas sticky note. Present only in the full document.
      required: [uuid, content, color, order, width, height, left, top, parent_uuid, font_size, record_type]
      properties:
        uuid: { type: string, format: uuid }
        content: { type: [string, "null"] }
        color: { type: string }
        order: { type: string }
        width: { type: integer }
        height: { type: integer }
        left: { type: integer }
        top: { type: integer }
        parent_uuid: { type: [string, "null"] }
        font_size: { type: integer }
        record_type: { type: string, examples: [sticky-note] }
