{
  "openapi": "3.1.0",
  "info": {
    "title": "Lettrove server API",
    "version": "1",
    "summary": "Tokens, designs, exports, rendering, erasure and webhooks, for your server.",
    "description": "What your server does with a Lettrove secret key: mint the editor's tokens, read a person's designs, export and render them, and erase a person. Plus the webhooks Lettrove sends you.\n\nCall it only from your server: the secret key never goes to a browser, and the API answers no browser (no CORS, by design). Within v1, fields are only ever added: ignore ones you do not know. Guides: https://docs.lettrove.com/docs/1/server/server-api.\n\nError codes: `design_invalid`, `design_not_found`, `key_invalid`, `mode_unavailable`, `origin_not_allowed`, `project_suspended`, `rate_limited`, `request_invalid`, `service_unavailable`, `user_erased`, `workspace_suspended`. Each is explained on https://docs.lettrove.com/docs/1/api/errors.",
    "contact": {
      "name": "Lettrove support",
      "email": "support@lettrove.com",
      "url": "https://docs.lettrove.com/docs/1"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "externalDocs": {
    "description": "The Lettrove documentation",
    "url": "https://docs.lettrove.com/docs/1"
  },
  "servers": [
    {
      "url": "https://api.lettrove.com",
      "description": "Production. Test and live are told apart by the key, not the address."
    }
  ],
  "security": [
    {
      "secretKey": []
    }
  ],
  "tags": [
    {
      "name": "Server API",
      "description": "Called by your server with its secret key."
    },
    {
      "name": "Webhooks",
      "description": "Sent by Lettrove to the URL you set on the project. Each is signed; a retry has the same `id`."
    }
  ],
  "paths": {
    "/embed/v1/tokens": {
      "post": {
        "operationId": "createToken",
        "summary": "Create a token",
        "description": "Exchange your secret key for a short-lived editor token for one person on one site. Your page asks your server for it (`getToken`), and the editor opens with it. Never cached.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 600,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "description": "`request_invalid`: The body does not match; the message names the field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.\n\n`origin_not_allowed`: The origin is not one of the project's allowed sites for this key's environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`user_erased`: This person is being erased.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed/v1/users/{userId}/designs": {
      "get": {
        "operationId": "listDesigns",
        "summary": "List a person's designs",
        "description": "One person's designs, newest first, a page at a time. A person Lettrove has never seen has none.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 600,
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "examples": [
                "u_123"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Designs per page, 1 to 200. Default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of designs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignList"
                }
              }
            }
          },
          "400": {
            "description": "`request_invalid`: The user id or the cursor is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`user_erased`: This person is being erased.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed/v1/users/{userId}/designs/{designId}": {
      "get": {
        "operationId": "getDesign",
        "summary": "Get a design",
        "description": "One design as last saved, with its document: your own copy.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 600,
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "examples": [
                "u_123"
              ]
            }
          },
          {
            "name": "designId",
            "in": "path",
            "required": true,
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "schema": {
              "type": "string",
              "examples": [
                "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The design.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Design"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`design_not_found`: No design with that id is this person's. Another person's design is not found either, never refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`user_erased`: This person is being erased.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed/v1/designs/{id}/export": {
      "post": {
        "operationId": "exportDesign",
        "summary": "Export a design",
        "description": "One person's design as last saved: as HTML (the default), or as a PDF, PNG or ZIP file kept for a URL. Recorded, and sent as an `export.created` webhook.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 120,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "schema": {
              "type": "string",
              "examples": [
                "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The HTML, or the file (`format` is set on a file).",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/HtmlExport"
                    },
                    {
                      "$ref": "#/components/schemas/FileExport"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`request_invalid`: The body does not match; the message names the field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`design_not_found`: No design with that id is this person's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`mode_unavailable`: A format this mode does not make, such as a popup's PDF.\n\n`user_erased`: This person is being erased.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed/v1/render": {
      "post": {
        "operationId": "renderDesign",
        "summary": "Render a design you hold",
        "description": "A design you keep yourself, as the file you send: the same export as everywhere else, by the project's mode. Nothing is stored with Lettrove: an inline image stays inline and is named in `warnings`.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 120,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered design.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rendered"
                }
              }
            }
          },
          "400": {
            "description": "`request_invalid`: The body does not match; the message names the field.\n\n`design_invalid`: `doc` is not a design.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`mode_unavailable`: The design is of another mode than the project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed/v1/users/{userId}": {
      "delete": {
        "operationId": "eraseUser",
        "summary": "Erase a person",
        "description": "Erase one person and everything they made: designs, restore points and images, at once, with no grace period. Asking again while it runs is the same request. A `user.erased` webhook follows when it is done.",
        "tags": [
          "Server API"
        ],
        "x-rate-limit-per-minute": 60,
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "examples": [
                "u_123"
              ]
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Erasure has started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erasing"
                }
              }
            }
          },
          "204": {
            "description": "Lettrove never saw this id: there is nothing to erase."
          },
          "400": {
            "description": "`request_invalid`: The user id is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`key_invalid`: The secret key is missing, revoked or not one Lettrove issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`project_suspended`: The project is suspended.\n\n`workspace_suspended`: Lettrove has suspended the embed for the workspace that owns the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Too many requests for this key; `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: Lettrove cannot do this right now (the embed is not available, or a file format or the queue is briefly down). Nothing was changed; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "design.saved": {
      "post": {
        "operationId": "webhook_design_saved",
        "summary": "design.saved",
        "description": "A design was created or saved.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "Lettrove-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the webhook's secret>`. Check it before trusting the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The delivery's id. A retry has the same id: skip ones you have handled.",
                    "examples": [
                      "01J9ZV8R1K3M5P7T9W2Y4A6C8E"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "const": "design.saved"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When it happened.",
                    "examples": [
                      "2026-10-07T16:03:10.000Z"
                    ]
                  },
                  "projectId": {
                    "type": "string",
                    "description": "Your project.",
                    "examples": [
                      "01J9X2B4D6F8H0K2M4P6R8T0V2"
                    ]
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "designId": {
                        "type": "string",
                        "description": "The id Lettrove gave the design: 26 letters and digits.",
                        "examples": [
                          "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
                        ]
                      },
                      "userId": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                        "examples": [
                          "u_123"
                        ]
                      },
                      "revision": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Its saved revision; only ever goes up. Saves of one design still waiting to be delivered are merged: you get the newest.",
                        "examples": [
                          12
                        ]
                      }
                    },
                    "required": [
                      "designId",
                      "userId",
                      "revision"
                    ]
                  }
                },
                "required": [
                  "id",
                  "type",
                  "createdAt",
                  "projectId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Answer with any 2xx status to say it arrived. Anything else, or no answer within 10 seconds, is retried with backoff, up to 8 attempts."
          }
        }
      }
    },
    "design.deleted": {
      "post": {
        "operationId": "webhook_design_deleted",
        "summary": "design.deleted",
        "description": "A design was deleted.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "Lettrove-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the webhook's secret>`. Check it before trusting the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The delivery's id. A retry has the same id: skip ones you have handled.",
                    "examples": [
                      "01J9ZV8R1K3M5P7T9W2Y4A6C8E"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "const": "design.deleted"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When it happened.",
                    "examples": [
                      "2026-10-07T16:03:10.000Z"
                    ]
                  },
                  "projectId": {
                    "type": "string",
                    "description": "Your project.",
                    "examples": [
                      "01J9X2B4D6F8H0K2M4P6R8T0V2"
                    ]
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "designId": {
                        "type": "string",
                        "description": "The id Lettrove gave the design: 26 letters and digits.",
                        "examples": [
                          "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
                        ]
                      },
                      "userId": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                        "examples": [
                          "u_123"
                        ]
                      }
                    },
                    "required": [
                      "designId",
                      "userId"
                    ]
                  }
                },
                "required": [
                  "id",
                  "type",
                  "createdAt",
                  "projectId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Answer with any 2xx status to say it arrived. Anything else, or no answer within 10 seconds, is retried with backoff, up to 8 attempts."
          }
        }
      }
    },
    "export.created": {
      "post": {
        "operationId": "webhook_export_created",
        "summary": "export.created",
        "description": "A design was exported, from the page or your server.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "Lettrove-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the webhook's secret>`. Check it before trusting the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The delivery's id. A retry has the same id: skip ones you have handled.",
                    "examples": [
                      "01J9ZV8R1K3M5P7T9W2Y4A6C8E"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "const": "export.created"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When it happened.",
                    "examples": [
                      "2026-10-07T16:03:10.000Z"
                    ]
                  },
                  "projectId": {
                    "type": "string",
                    "description": "Your project.",
                    "examples": [
                      "01J9X2B4D6F8H0K2M4P6R8T0V2"
                    ]
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "exportId": {
                        "type": "string",
                        "description": "The export's record.",
                        "examples": [
                          "01J9ZR4C2N8B6V0X3M7K5T9QWE"
                        ]
                      },
                      "designId": {
                        "type": "string",
                        "description": "The id Lettrove gave the design: 26 letters and digits.",
                        "examples": [
                          "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
                        ]
                      },
                      "userId": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                        "examples": [
                          "u_123"
                        ]
                      },
                      "revision": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "The saved revision; only ever goes up.",
                        "examples": [
                          12
                        ]
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "editor",
                          "server"
                        ],
                        "description": "`editor` (the page) or `server` (your secret key)."
                      },
                      "format": {
                        "type": "string",
                        "enum": [
                          "html",
                          "text",
                          "zip",
                          "pdf",
                          "png"
                        ],
                        "description": "`html`, `text` (a plain-text export), `pdf`, `png` or `zip`."
                      }
                    },
                    "required": [
                      "exportId",
                      "designId",
                      "userId",
                      "revision",
                      "source",
                      "format"
                    ]
                  }
                },
                "required": [
                  "id",
                  "type",
                  "createdAt",
                  "projectId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Answer with any 2xx status to say it arrived. Anything else, or no answer within 10 seconds, is retried with backoff, up to 8 attempts."
          }
        }
      }
    },
    "user.erased": {
      "post": {
        "operationId": "webhook_user_erased",
        "summary": "user.erased",
        "description": "A person's data has been erased, after your erase request.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "Lettrove-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the webhook's secret>`. Check it before trusting the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The delivery's id. A retry has the same id: skip ones you have handled.",
                    "examples": [
                      "01J9ZV8R1K3M5P7T9W2Y4A6C8E"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "const": "user.erased"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When it happened.",
                    "examples": [
                      "2026-10-07T16:03:10.000Z"
                    ]
                  },
                  "projectId": {
                    "type": "string",
                    "description": "Your project.",
                    "examples": [
                      "01J9X2B4D6F8H0K2M4P6R8T0V2"
                    ]
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "userId": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                        "examples": [
                          "u_123"
                        ]
                      }
                    },
                    "required": [
                      "userId"
                    ]
                  }
                },
                "required": [
                  "id",
                  "type",
                  "createdAt",
                  "projectId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Answer with any 2xx status to say it arrived. Anything else, or no answer within 10 seconds, is retried with backoff, up to 8 attempts."
          }
        }
      }
    },
    "ping": {
      "post": {
        "operationId": "webhook_ping",
        "summary": "ping",
        "description": "Sent by **Send a test** in the dashboard, whatever events the webhook subscribes to.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "Lettrove-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the webhook's secret>`. Check it before trusting the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The delivery's id. A retry has the same id: skip ones you have handled.",
                    "examples": [
                      "01J9ZV8R1K3M5P7T9W2Y4A6C8E"
                    ]
                  },
                  "type": {
                    "type": "string",
                    "const": "ping"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When it happened.",
                    "examples": [
                      "2026-10-07T16:03:10.000Z"
                    ]
                  },
                  "projectId": {
                    "type": "string",
                    "description": "Your project.",
                    "examples": [
                      "01J9X2B4D6F8H0K2M4P6R8T0V2"
                    ]
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "message": {
                        "type": "string",
                        "description": "Always this text.",
                        "examples": [
                          "A test event from Lettrove."
                        ]
                      }
                    },
                    "required": [
                      "message"
                    ]
                  }
                },
                "required": [
                  "id",
                  "type",
                  "createdAt",
                  "projectId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Answer with any 2xx status to say it arrived. Anything else, or no answer within 10 seconds, is retried with backoff, up to 8 attempts."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "secretKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "lt_sk_test_… or lt_sk_live_…",
        "description": "Your project's secret key, from your server only."
      }
    },
    "schemas": {
      "Design": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "examples": [
              "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
            ]
          },
          "name": {
            "type": "string",
            "description": "The name the person gave it.",
            "examples": [
              "October newsletter"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "email",
              "page",
              "popup",
              "document"
            ],
            "description": "What the design is: `email`, `page`, `popup` or `document`. A project makes one mode."
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "The saved revision; only ever goes up.",
            "examples": [
              12
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601, UTC.",
            "examples": [
              "2026-10-07T16:03:10.000Z"
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601, UTC.",
            "examples": [
              "2026-10-07T16:03:10.000Z"
            ]
          },
          "doc": {
            "$ref": "#/components/schemas/DesignDocument"
          }
        },
        "required": [
          "id",
          "name",
          "mode",
          "revision",
          "createdAt",
          "updatedAt",
          "doc"
        ]
      },
      "DesignDocument": {
        "type": "object",
        "additionalProperties": {},
        "description": "The design document. Store it as it is; what you may rely on inside it is on docs.lettrove.com/docs/1/api/design-document."
      },
      "DesignList": {
        "type": "object",
        "properties": {
          "designs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DesignSummary"
            },
            "description": "Newest first. Empty for a person Lettrove has never seen."
          },
          "nextCursor": {
            "description": "Pass as `cursor` for the next page; `null` on the last.",
            "examples": [
              null
            ],
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "designs",
          "nextCursor"
        ]
      },
      "DesignSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "examples": [
              "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
            ]
          },
          "name": {
            "type": "string",
            "description": "The name the person gave it.",
            "examples": [
              "October newsletter"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "email",
              "page",
              "popup",
              "document"
            ],
            "description": "What the design is: `email`, `page`, `popup` or `document`. A project makes one mode."
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "The saved revision; only ever goes up.",
            "examples": [
              12
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601, UTC.",
            "examples": [
              "2026-10-07T16:03:10.000Z"
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601, UTC.",
            "examples": [
              "2026-10-07T16:03:10.000Z"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "mode",
          "revision",
          "createdAt",
          "updatedAt"
        ]
      },
      "Erasing": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "erasing"
          }
        },
        "required": [
          "status"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, in words. May be reworded; match on `code`.",
            "examples": [
              "user.email is not allowed: an end user is an opaque id."
            ]
          },
          "code": {
            "description": "Stable. Every code is on docs.lettrove.com/docs/1/api/errors.",
            "examples": [
              "request_invalid"
            ],
            "type": "string"
          },
          "requestId": {
            "description": "Quote it to support.",
            "examples": [
              "req_01J9ZT6W2Q"
            ],
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "ExportRequest": {
        "type": "object",
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 256,
                "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                "examples": [
                  "u_123"
                ]
              }
            },
            "required": [
              "id"
            ],
            "additionalProperties": false,
            "description": "Whose design it is. Another person's design is not found."
          },
          "merge": {
            "$ref": "#/components/schemas/Merge"
          },
          "text": {
            "$ref": "#/components/schemas/TextOptions"
          },
          "format": {
            "description": "`html` (the default) answers with the HTML; `pdf`, `png` or `zip` with a file kept for a URL.",
            "examples": [
              "html"
            ],
            "type": "string",
            "enum": [
              "html",
              "zip",
              "pdf",
              "png"
            ]
          },
          "fullPage": {
            "description": "With `png`: the whole design (`true`, the default) or the first screen only.",
            "type": "boolean"
          }
        },
        "required": [
          "user"
        ],
        "additionalProperties": false
      },
      "FileExport": {
        "type": "object",
        "properties": {
          "exportId": {
            "type": "string",
            "description": "The export's record.",
            "examples": [
              "01J9ZR4C2N8B6V0X3M7K5T9QWE"
            ]
          },
          "designId": {
            "type": "string",
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "examples": [
              "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
            ]
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "The saved revision; only ever goes up.",
            "examples": [
              12
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "email",
              "page",
              "popup",
              "document"
            ],
            "description": "What the design is: `email`, `page`, `popup` or `document`. A project makes one mode."
          },
          "format": {
            "type": "string",
            "enum": [
              "zip",
              "pdf",
              "png"
            ],
            "description": "The file it is.",
            "examples": [
              "pdf"
            ]
          },
          "url": {
            "type": "string",
            "description": "Where to fetch the file.",
            "examples": [
              "https://files.lettrove.com/embed/…/q3-proposal.pdf"
            ]
          },
          "expiresAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601, UTC.",
                "examples": [
                  "2026-10-07T16:03:10.000Z"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "When the URL stops working; `null` while it is kept.",
            "examples": [
              null
            ]
          },
          "filename": {
            "type": "string",
            "description": "A safe file name from the design's name.",
            "examples": [
              "q3-proposal.pdf"
            ]
          },
          "bytes": {
            "type": "integer",
            "description": "Size of the file in bytes.",
            "examples": [
              48213
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Things worth knowing, in words: an email past Gmail's clipping size, an inline image left inline."
          },
          "design": {
            "$ref": "#/components/schemas/DesignDocument"
          }
        },
        "required": [
          "exportId",
          "designId",
          "revision",
          "mode",
          "format",
          "url",
          "expiresAt",
          "filename",
          "bytes",
          "warnings",
          "design"
        ],
        "title": "As a file"
      },
      "HtmlChunks": {
        "type": "object",
        "properties": {
          "body": {
            "type": "string",
            "description": "What goes inside `<body>`, its scripts taken out."
          },
          "css": {
            "type": "string",
            "description": "Every stylesheet the design carries, in order."
          },
          "js": {
            "type": "string",
            "description": "Every script it runs, in order; empty for an email.",
            "examples": [
              ""
            ]
          },
          "fonts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The web-font stylesheets it links to, by URL."
          }
        },
        "required": [
          "body",
          "css",
          "js",
          "fonts"
        ],
        "description": "The HTML in pieces, to place it inside a page of your own."
      },
      "HtmlExport": {
        "type": "object",
        "properties": {
          "exportId": {
            "type": "string",
            "description": "The export's record.",
            "examples": [
              "01J9ZR4C2N8B6V0X3M7K5T9QWE"
            ]
          },
          "designId": {
            "type": "string",
            "description": "The id Lettrove gave the design: 26 letters and digits.",
            "examples": [
              "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
            ]
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "The saved revision; only ever goes up.",
            "examples": [
              12
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "email",
              "page",
              "popup",
              "document"
            ],
            "description": "What the design is: `email`, `page`, `popup` or `document`. A project makes one mode."
          },
          "html": {
            "type": "string",
            "description": "The whole document, ready to send or serve.",
            "examples": [
              "<!doctype html>…"
            ]
          },
          "chunks": {
            "$ref": "#/components/schemas/HtmlChunks"
          },
          "text": {
            "type": "string",
            "description": "The plain-text version (the `text/plain` part), merged the same way.",
            "examples": [
              "Hi Ada, your order has shipped…"
            ]
          },
          "subject": {
            "type": "string",
            "description": "The subject line, merged.",
            "examples": [
              "Your order has shipped"
            ]
          },
          "preheader": {
            "type": "string",
            "description": "The preview text after the subject, merged.",
            "examples": [
              "It arrives Thursday."
            ]
          },
          "mergeTags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags still in the HTML, text, subject or preheader, exactly as written: yours to fill before it goes to anyone."
          },
          "rendererVersion": {
            "type": "integer",
            "description": "Which renderer made it. Goes up when the output changes.",
            "examples": [
              7
            ]
          },
          "bytes": {
            "type": "integer",
            "description": "Size of `html` in bytes.",
            "examples": [
              38211
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Things worth knowing, in words: an email past Gmail's clipping size, an inline image left inline."
          },
          "design": {
            "description": "The design document exported. Store it as it is.",
            "$ref": "#/components/schemas/DesignDocument"
          }
        },
        "required": [
          "exportId",
          "designId",
          "revision",
          "mode",
          "html",
          "chunks",
          "text",
          "subject",
          "preheader",
          "mergeTags",
          "rendererVersion",
          "bytes",
          "warnings",
          "design"
        ],
        "title": "As HTML"
      },
      "Merge": {
        "type": "object",
        "properties": {
          "contact": {
            "description": "The values for `{{contact.<field>}}` tags, by field.",
            "examples": [
              {
                "first_name": "Ada"
              }
            ],
            "type": "object",
            "propertyNames": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            }
          },
          "unsubscribeUrl": {
            "description": "Your unsubscribe link, for `{{unsubscribe_url}}`. Left out, the tag stays for your sending provider to fill.",
            "examples": [
              "https://acme.com/u/123"
            ],
            "type": "string",
            "maxLength": 2048,
            "format": "uri"
          },
          "preferencesUrl": {
            "description": "Your preferences link, for `{{preferences_url}}`. Left out, the tag stays.",
            "type": "string",
            "maxLength": 2048,
            "format": "uri"
          }
        },
        "additionalProperties": false,
        "description": "Merge tags to fill. A tag with no value stays as written and is listed in `mergeTags`."
      },
      "Rendered": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "email",
              "page",
              "popup",
              "document"
            ],
            "description": "What the design is: `email`, `page`, `popup` or `document`. A project makes one mode."
          },
          "html": {
            "type": "string",
            "description": "The whole document, ready to send or serve.",
            "examples": [
              "<!doctype html>…"
            ]
          },
          "chunks": {
            "$ref": "#/components/schemas/HtmlChunks"
          },
          "text": {
            "type": "string",
            "description": "The plain-text version (the `text/plain` part), merged the same way.",
            "examples": [
              "Hi Ada, your order has shipped…"
            ]
          },
          "subject": {
            "type": "string",
            "description": "The subject line, merged.",
            "examples": [
              "Your order has shipped"
            ]
          },
          "preheader": {
            "type": "string",
            "description": "The preview text after the subject, merged.",
            "examples": [
              "It arrives Thursday."
            ]
          },
          "mergeTags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags still in the HTML, text, subject or preheader, exactly as written: yours to fill before it goes to anyone."
          },
          "rendererVersion": {
            "type": "integer",
            "description": "Which renderer made it. Goes up when the output changes.",
            "examples": [
              7
            ]
          },
          "bytes": {
            "type": "integer",
            "description": "Size of `html` in bytes.",
            "examples": [
              38211
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Things worth knowing, in words: an email past Gmail's clipping size, an inline image left inline."
          },
          "design": {
            "description": "The design document exported. Store it as it is.",
            "$ref": "#/components/schemas/DesignDocument"
          }
        },
        "required": [
          "mode",
          "html",
          "chunks",
          "text",
          "subject",
          "preheader",
          "mergeTags",
          "rendererVersion",
          "bytes",
          "warnings",
          "design"
        ],
        "description": "An HTML export, without a record: nothing is stored."
      },
      "RenderRequest": {
        "type": "object",
        "properties": {
          "doc": {
            "$ref": "#/components/schemas/DesignDocument",
            "description": "The design document, as you hold it."
          },
          "designId": {
            "description": "The id you know this design by. A popup's handle and a form's event carry it. Left out, the document's own id.",
            "examples": [
              "01J9ZQ3W8D2K7M5T1V4XG6HB0R"
            ],
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,64}$"
          },
          "merge": {
            "$ref": "#/components/schemas/Merge"
          },
          "text": {
            "$ref": "#/components/schemas/TextOptions"
          }
        },
        "required": [
          "doc"
        ],
        "additionalProperties": false
      },
      "TextOptions": {
        "type": "object",
        "properties": {
          "links": {
            "description": "Each link's address after its text.",
            "type": "boolean"
          },
          "images": {
            "description": "Images' alt text.",
            "type": "boolean"
          },
          "preheader": {
            "description": "The preheader first.",
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "description": "How the plain-text version is written."
      },
      "Token": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "Hand it to the page, which passes it to the editor. Never store it.",
            "examples": [
              "eyJhbGciOiJFZERTQSIs…"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it stops working. The page asks your server for a new one before then.",
            "examples": [
              "2026-10-07T16:15:00.000Z"
            ]
          }
        },
        "required": [
          "token",
          "expiresAt"
        ]
      },
      "TokenRequest": {
        "type": "object",
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 256,
                "description": "Your own id for the person: whatever you chose, up to 256 characters. Never a name or an email.",
                "examples": [
                  "u_123"
                ]
              }
            },
            "required": [
              "id"
            ],
            "additionalProperties": false,
            "description": "The person the editor opens for."
          },
          "origin": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "The site the editor opens on, exactly as a browser states it: scheme, host and port, no path. It must be one of the project's allowed sites; a test key also opens on localhost.",
            "examples": [
              "https://app.acme.com"
            ]
          },
          "ttl": {
            "description": "Seconds the token lasts, 60 to 900. Default 900.",
            "examples": [
              900
            ],
            "type": "integer",
            "minimum": 60,
            "maximum": 900
          }
        },
        "required": [
          "user",
          "origin"
        ],
        "additionalProperties": false
      }
    }
  }
}
