openapi: 3.0.3
info:
  title: JotBird API
  description: |
    Publish, manage, and remove Markdown documents programmatically.

    All endpoints require a personal API key passed as a Bearer token.
    Generate a key by running `jotbird login` or visiting
    [jotbird.com/account/api-key](https://www.jotbird.com/account/api-key).
  version: 1.0.0
  contact:
    email: support@jotbird.com
    url: https://www.jotbird.com/docs/api

servers:
  - url: https://www.jotbird.com
    description: Production

security:
  - apiKey: []

paths:
  /api/v1/publish:
    post:
      operationId: publish
      summary: Publish a document
      description: |
        Publish a new Markdown document or update an existing one. The server
        renders Markdown to HTML and hosts it at a shareable URL on
        `share.jotbird.com`.

        A URL slug is generated automatically. To update an existing document,
        pass its slug in the request body.

        **Rate limits:** Free accounts may publish up to 10 times per hour
        (Pro: 100). Free accounts are limited to 10 active documents.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [markdown]
              properties:
                markdown:
                  type: string
                  description: The Markdown content to publish.
                title:
                  type: string
                  description: Document title. If omitted, extracted from the first H1 in the Markdown.
                slug:
                  type: string
                  description: Slug of an existing document to update. If the slug exists and is owned by you, the document is updated in place. Ignored for new documents.
            examples:
              newDocument:
                summary: Publish a new document
                value:
                  markdown: "# Hello World\n\nThis is my document."
              updateExisting:
                summary: Update an existing document by slug
                value:
                  markdown: "# Hello World\n\nUpdated content goes here."
                  slug: bright-calm-meadow
      responses:
        "201":
          description: New document published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishResponse"
              example:
                slug: bright-calm-meadow
                url: https://share.jotbird.com/bright-calm-meadow
                title: Hello World
                expiresAt: "2026-05-10T12:00:00.000Z"
                ttlDays: 90
                created: true
        "200":
          description: Existing document updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"

  /api/v1/documents:
    get:
      operationId: listDocuments
      summary: List documents
      description: |
        Retrieve all published documents for your account. Only documents with
        an active public URL are returned, ordered by most recently updated.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListResponse"
              example:
                documents:
                  - slug: bright-calm-meadow
                    title: Hello World
                    url: https://share.jotbird.com/bright-calm-meadow
                    source: cli
                    updatedAt: "2026-02-09T14:30:00.000Z"
                    expiresAt: "2026-05-10T14:30:00.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"

    delete:
      operationId: removeDocument
      summary: Remove a document
      description: |
        Permanently delete a document, its public URL, and all associated image
        references. This action cannot be undone.
      parameters:
        - name: slug
          in: query
          required: true
          schema:
            type: string
          description: The slug of the document to permanently remove.
          example: bright-calm-meadow
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OkResponse"
              example:
                ok: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Document not found

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "Personal API key (prefix: `jb_`)"

  schemas:
    PublishResponse:
      type: object
      properties:
        slug:
          type: string
          description: Auto-generated URL slug.
          example: bright-calm-meadow
        url:
          type: string
          format: uri
          description: Public URL on share.jotbird.com.
          example: https://share.jotbird.com/bright-calm-meadow
        title:
          type: string
          description: Document title.
          example: Hello World
        expiresAt:
          type: string
          format: date-time
          nullable: true
          description: When the document expires. Null for Pro accounts.
        ttlDays:
          type: integer
          description: TTL in days (90 for authenticated users).
          example: 90
        created:
          type: boolean
          description: True if a new document was created, false if an existing one was updated.

    ListResponse:
      type: object
      properties:
        documents:
          type: array
          items:
            $ref: "#/components/schemas/Document"

    Document:
      type: object
      properties:
        slug:
          type: string
          example: bright-calm-meadow
        title:
          type: string
          example: Hello World
        url:
          type: string
          format: uri
          example: https://share.jotbird.com/bright-calm-meadow
        source:
          type: string
          enum: [cli, web, api]
          description: How the document was created.
        updatedAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
          nullable: true

    OkResponse:
      type: object
      properties:
        ok:
          type: boolean
          example: true

    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
          example: Missing or invalid slug

  responses:
    BadRequest:
      description: Invalid JSON, missing required fields, or invalid format.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: Document exists but is owned by another account.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    PayloadTooLarge:
      description: Rendered HTML exceeds 512 KB.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    TooManyRequests:
      description: Rate limit exceeded (10/hour free, 100/hour pro).
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the rate limit window resets.
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Maximum requests per hour.
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Requests remaining in the current window.
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp when the window resets.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
