{
  "openapi": "3.1.0",
  "info": {
    "title": "DynaPictures API",
    "version": "1.0.0",
    "summary": "Generate images, PDFs and AI images from templates and data.",
    "description": "Design a template once in DynaPictures, then fill it with data through this API. Send your API key as `Authorization: Bearer <key>`; see https://dynapictures.com/auth.md.\n\n**Errors** are JSON with a `message` field.\n\n**Limits.** `POST /designs/{uid}` reports the monthly image quota in `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds). When the quota is used up the API answers 403. `GET /links` answers 503 with `Retry-After` when busy.\n\n**Versioning.** URLs carry no version. Changes are additive: new endpoints and optional fields can appear at any time, so ignore fields you don't recognize. Removing an endpoint or field is announced in the API reference at least 6 months ahead, and its responses carry `Deprecation` and `Sunset` headers during that time.",
    "termsOfService": "https://dynapictures.com/p/terms",
    "contact": {
      "name": "DynaPictures support",
      "email": "info@dynapictures.com",
      "url": "https://dynapictures.com/p/contact"
    }
  },
  "externalDocs": {
    "description": "API reference",
    "url": "https://dynapictures.com/docs/"
  },
  "servers": [
    {
      "url": "https://api.dynapictures.com"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Images",
      "description": "Render templates into images and PDFs."
    },
    {
      "name": "AI",
      "description": "Generate standalone AI images."
    },
    {
      "name": "Templates",
      "description": "Find templates and their layers."
    },
    {
      "name": "Design authoring",
      "description": "Create and edit designs without the editor."
    },
    {
      "name": "Workspaces",
      "description": "Organize designs and media."
    },
    {
      "name": "Media library",
      "description": "Store images for use in designs."
    },
    {
      "name": "Webhooks",
      "description": "Get notified about new images."
    }
  ],
  "paths": {
    "/designs/{uid}": {
      "post": {
        "operationId": "generateImage",
        "tags": [
          "Images"
        ],
        "summary": "Generate an image from a template",
        "description": "Renders a template with your data and returns the image URLs. Works for single-page templates (`params`), multipage templates (`pages`) and PDFs (`format: pdf`). Each image or PDF page uses one image credit. Requests are not idempotent, so a repeated request uses another credit.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateImageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated image. With `responseFormat: binary`, the image bytes.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GeneratedImage"
                    },
                    {
                      "$ref": "#/components/schemas/MultipageResult"
                    }
                  ]
                }
              },
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/RenderFailed"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        }
      }
    },
    "/batch": {
      "post": {
        "operationId": "generateBatchPdf",
        "tags": [
          "Images"
        ],
        "summary": "Generate a PDF from many pages",
        "description": "Renders any number of pages, from one template or from several, into one PDF returned in the response. Pages render one after another, and each uses one image credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The PDF file.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/RenderFailed"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        }
      }
    },
    "/links/{uid}.{format}": {
      "get": {
        "operationId": "renderImageLink",
        "tags": [
          "Images"
        ],
        "summary": "Render an image from a URL",
        "description": "Generates an image with a single GET, so it works inside an `<img>` tag. Needs no API key. Identical URLs return a cached image and use no further credit. If the quota is exhausted and the design has a fallback image, that image is served instead.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "description": "The output format.",
            "schema": {
              "type": "string",
              "enum": [
                "png",
                "jpeg",
                "webp",
                "avif",
                "gif",
                "pdf",
                "pptx"
              ]
            }
          },
          {
            "name": "params",
            "in": "query",
            "description": "Layer overrides as `{layer}---{property}___{value}`, joined with `<>`. The whole value must be URL-encoded.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "metadata",
            "in": "query",
            "description": "Custom data stored on the generated image.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rendered file.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/RenderFailed"
          },
          "503": {
            "$ref": "#/components/responses/ServerBusy"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        }
      }
    },
    "/generated-images": {
      "get": {
        "operationId": "searchGeneratedImages",
        "tags": [
          "Images"
        ],
        "summary": "Search generated images",
        "description": "Finds images generated by the caller's teams, oldest first, up to 200 per request. Narrow a large result with `startDate` and `endDate`.",
        "parameters": [
          {
            "name": "keyword",
            "in": "query",
            "description": "Full-text search in metadata and parameters.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "metadata",
            "in": "query",
            "description": "Exact match on the metadata.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "description": "Generated at or after this time, in ISO 8601.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "description": "Generated before this time, in ISO 8601.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matching images.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "description": "The matching images.",
                  "items": {
                    "$ref": "#/components/schemas/ImageSearchResult"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/images/{teamUid}/{filename}": {
      "delete": {
        "operationId": "deleteGeneratedImage",
        "tags": [
          "Images"
        ],
        "summary": "Delete a generated image",
        "description": "Permanently deletes a generated image. Both path parts come from the image URL: for `https://edge.dynapictures.com/ff01d10f8d/9be4738250.png` they are `ff01d10f8d` and `9be4738250.png`.",
        "parameters": [
          {
            "name": "teamUid",
            "in": "path",
            "required": true,
            "description": "The first path segment of the image URL.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filename",
            "in": "path",
            "required": true,
            "description": "The file name in the image URL.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image was deleted. The response has no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/ai/generate": {
      "post": {
        "operationId": "startAiGeneration",
        "tags": [
          "AI"
        ],
        "summary": "Start an AI image generation",
        "description": "Starts generating a standalone image, not tied to a template, and returns immediately. Poll the `Location` URL every 2 seconds or so until `status` is `done` or `error`; generation usually takes 10 to 60 seconds. Uses AI credits, charged only on success.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The job was queued.",
            "headers": {
              "Location": {
                "description": "Where to poll for the result.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiJobStarted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The workspace is out of AI credits. No job was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ai/generate/{jobUid}": {
      "get": {
        "operationId": "getAiGeneration",
        "tags": [
          "AI"
        ],
        "summary": "Get an AI generation job",
        "description": "Returns the state of a job, and the result URL once it is done.",
        "parameters": [
          {
            "name": "jobUid",
            "in": "path",
            "required": true,
            "description": "The `jobUid` from the start request.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiJob"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "summary": "List templates",
        "description": "Lists the templates with \"Sync to Zapier\" enabled, with their layer names.",
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "description": "Only this workspace.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "description": "The templates.",
                  "items": {
                    "$ref": "#/components/schemas/Template"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/templates/{uid}": {
      "get": {
        "operationId": "getTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Get a template",
        "description": "Returns one template with its layer names, which are what `POST /designs/{uid}` overrides.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/designs": {
      "post": {
        "operationId": "createDesign",
        "tags": [
          "Design authoring"
        ],
        "summary": "Create an empty design",
        "description": "Creates a design with an empty canvas. Add layers to it, then render it with `POST /designs/{uid}`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDesignRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new design.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Design"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/designs/{uid}/layers": {
      "post": {
        "operationId": "addLayer",
        "tags": [
          "Design authoring"
        ],
        "summary": "Add a layer",
        "description": "Adds a text, image, AI image, rectangle or circle layer to a design.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddLayerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new layer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LayerResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/designs/{uid}/layers/{layerId}": {
      "patch": {
        "operationId": "updateLayer",
        "tags": [
          "Design authoring"
        ],
        "summary": "Update a layer",
        "description": "Changes the position, size, text or styles of a layer. Only the fields you send change.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          },
          {
            "$ref": "#/components/parameters/LayerId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchLayerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated layer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LayerResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteLayer",
        "tags": [
          "Design authoring"
        ],
        "summary": "Delete a layer",
        "description": "Removes a layer from a design.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          },
          {
            "$ref": "#/components/parameters/LayerId"
          }
        ],
        "responses": {
          "200": {
            "description": "The layer was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LayerDeleted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/designs/{uid}/layers/{layerId}/z-index": {
      "post": {
        "operationId": "reorderLayer",
        "tags": [
          "Design authoring"
        ],
        "summary": "Change a layer's stacking order",
        "description": "Moves a layer in front of or behind the others.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          },
          {
            "$ref": "#/components/parameters/LayerId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderLayerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved layer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LayerResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/designs/{uid}/canvas/resize": {
      "post": {
        "operationId": "resizeCanvas",
        "tags": [
          "Design authoring"
        ],
        "summary": "Resize the canvas",
        "description": "Changes the canvas size of a design.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateUid"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResizeCanvasRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new size.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CanvasResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/workspaces": {
      "get": {
        "operationId": "listWorkspaces",
        "tags": [
          "Workspaces"
        ],
        "summary": "List workspaces",
        "description": "Lists the workspaces the caller belongs to.",
        "responses": {
          "200": {
            "description": "The workspaces.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "description": "The workspaces.",
                  "items": {
                    "$ref": "#/components/schemas/Workspace"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createWorkspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Create a workspace",
        "description": "Creates a workspace. Account admins only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/workspaces/{workspaceId}": {
      "put": {
        "operationId": "updateWorkspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Rename a workspace",
        "description": "Changes the name of a workspace. Account admins only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteWorkspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Delete a workspace",
        "description": "Permanently deletes a workspace with all its templates, media and generated images. This cannot be undone. Workspace admins only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/media/{workspaceId}/assets": {
      "get": {
        "operationId": "listMediaAssets",
        "tags": [
          "Media library"
        ],
        "summary": "List media assets",
        "description": "Lists the images and folders of a workspace, 30 per page.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "name": "p",
            "in": "query",
            "description": "The page, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of assets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAssetPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "uploadMediaAsset",
        "tags": [
          "Media library"
        ],
        "summary": "Upload an image",
        "description": "Uploads an image to the media library. Images already in the library render faster, because they don't have to be downloaded first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/MediaUpload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/media/{workspaceId}/assets/from-url": {
      "post": {
        "operationId": "uploadMediaAssetFromUrl",
        "tags": [
          "Media library"
        ],
        "summary": "Upload an image from a URL",
        "description": "Adds an image to the media library from a public URL or from base64.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaFromUrlRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/media/{workspaceId}/assets/{assetId}": {
      "get": {
        "operationId": "getMediaAsset",
        "tags": [
          "Media library"
        ],
        "summary": "Get a media asset",
        "description": "Returns one asset.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "$ref": "#/components/parameters/AssetId"
          }
        ],
        "responses": {
          "200": {
            "description": "The asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "replaceMediaAsset",
        "tags": [
          "Media library"
        ],
        "summary": "Replace a media asset",
        "description": "Uploads a new image for an existing asset.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "$ref": "#/components/parameters/AssetId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/MediaUpload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteMediaAsset",
        "tags": [
          "Media library"
        ],
        "summary": "Delete a media asset",
        "description": "Permanently deletes an asset.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "$ref": "#/components/parameters/AssetId"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaAsset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/hooks": {
      "post": {
        "operationId": "subscribeWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Subscribe a webhook",
        "description": "Sends a POST to `targetUrl` each time an image is generated from the template.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "operationId": "unsubscribeWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Unsubscribe a webhook",
        "description": "Removes a webhook. Send the values used to subscribe it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The webhook was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    }
  },
  "webhooks": {
    "newImage": {
      "post": {
        "operationId": "onNewImage",
        "tags": [
          "Webhooks"
        ],
        "summary": "A new image was generated",
        "description": "Sent to `targetUrl` for each image generated from the subscribed template.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GeneratedImage"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx status acknowledges the notification."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "Your DynaPictures API key, from https://dynapictures.com/app/account."
      }
    },
    "parameters": {
      "TemplateUid": {
        "name": "uid",
        "in": "path",
        "required": true,
        "description": "The ID of the template, shown in the editor and returned by `GET /templates`.",
        "schema": {
          "type": "string"
        }
      },
      "WorkspaceId": {
        "name": "workspaceId",
        "in": "path",
        "required": true,
        "description": "The ID of the workspace.",
        "schema": {
          "type": "string"
        }
      },
      "AssetId": {
        "name": "assetId",
        "in": "path",
        "required": true,
        "description": "The ID of the asset.",
        "schema": {
          "type": "string"
        }
      },
      "LayerId": {
        "name": "layerId",
        "in": "path",
        "required": true,
        "description": "The ID of the layer.",
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "The monthly image quota.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Images left in this period. It can trail real usage by a few seconds.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the quota resets.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid. `message` says why.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing or wrong.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "WWW-Authenticate": {
            "description": "Always `Bearer`.",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The plan limit is reached, the trial has expired, or the key has no access to this resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist, or the key has no access to it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RenderFailed": {
        "description": "The render failed. With `error: \"render_incomplete\"`, a layer could not be rendered: the credit is used, and retrying renders again.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerBusy": {
        "description": "The service is busy. Wait for the `Retry-After` value, then retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        }
      },
      "Timeout": {
        "description": "The render took longer than 120 seconds. The image may still have been generated, so check before retrying.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The error body of every endpoint. `message` is always present.",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "What went wrong, in plain language."
          },
          "error": {
            "type": [
              "boolean",
              "string"
            ],
            "description": "`true`, or a machine-readable code such as `render_incomplete`."
          },
          "code": {
            "type": "string",
            "description": "A machine-readable error code, when the failure has one."
          },
          "layers": {
            "type": "array",
            "description": "With `render_incomplete`: the layers that could not be rendered.",
            "items": {}
          }
        }
      },
      "Success": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "boolean",
            "description": "Always `false`."
          },
          "message": {
            "type": "string",
            "description": "Empty on success."
          }
        }
      },
      "LayerOverride": {
        "type": "object",
        "description": "New values for one template layer. Layers you leave out keep their design.",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the layer to customize, shown in the settings panel when the layer is selected. `canvas` is the background."
          },
          "text": {
            "type": "string",
            "description": "The text to render in a text layer. Basic HTML is supported and sanitized."
          },
          "color": {
            "type": "string",
            "description": "The text color. E.g. `#000000`, `rgb(0,0,0)` or `rgba(0,0,0,0.5)`."
          },
          "fontSize": {
            "type": "string",
            "description": "Font size of a text layer, e.g. `48px`."
          },
          "backgroundColor": {
            "type": "string",
            "description": "The background color of the layer. E.g. `#000000`, `rgb(0,0,0)` or `rgba(0,0,0,0.5)`."
          },
          "borderColor": {
            "type": "string",
            "description": "The border color of the layer. Set `borderWidth` as well. E.g. `#000000`, `rgb(0,0,0)` or `rgba(0,0,0,0.5)`."
          },
          "borderWidth": {
            "type": "string",
            "description": "The border width of the layer, e.g. `1px`."
          },
          "borderRadius": {
            "type": "string",
            "description": "The border radius of the layer, e.g. `5px` or `50%`."
          },
          "opacity": {
            "type": "number",
            "description": "Layer opacity from `0` (hidden) to `1` (as designed)."
          },
          "imageUrl": {
            "type": "string",
            "description": "The image to render in an image layer.",
            "format": "uri"
          },
          "imagePosition": {
            "type": "string",
            "description": "How the image fits its container. Empty centers it. `cro` crops to fill, `cov` scales to cover, `ai_face` detects faces and keeps them visible, and `align` uses `imageAlignH` and `imageAlignV`.",
            "enum": [
              "cro",
              "cov",
              "ai_face",
              "align"
            ]
          },
          "imageAlignH": {
            "type": "string",
            "description": "Horizontal image alignment when `imagePosition` is `align`.",
            "enum": [
              "left",
              "center",
              "right",
              "full"
            ]
          },
          "imageAlignV": {
            "type": "string",
            "description": "Vertical image alignment when `imagePosition` is `align`.",
            "enum": [
              "top",
              "center",
              "bottom",
              "full"
            ]
          },
          "imageEffect": {
            "type": "string",
            "description": "A CSS filter applied to the image, e.g. `grayscale(100%)` or `blur(4px)`."
          },
          "prompt": {
            "type": "string",
            "description": "Prompt for an AI Image layer in \"Per request\" mode. Supports `[name]` tokens that refer to `referenceImages`."
          },
          "sourceImage": {
            "type": "string",
            "description": "URL of a photo for an AI Image layer to edit. Omit it for text-to-image.",
            "format": "uri"
          },
          "referenceImages": {
            "type": "array",
            "description": "Named reference images for an AI Image layer.",
            "items": {
              "$ref": "#/components/schemas/ReferenceImage"
            }
          },
          "chartColor": {
            "type": "string",
            "description": "The color of the chart. E.g. `#000000`, `rgb(0,0,0)` or `rgba(0,0,0,0.5)`."
          },
          "chartLabelColor": {
            "type": "string",
            "description": "The color of the chart axis labels. E.g. `#000000`, `rgb(0,0,0)` or `rgba(0,0,0,0.5)`."
          },
          "chartDataLabels": {
            "type": "array",
            "description": "Labels shown on the X axis.",
            "items": {
              "type": "string"
            }
          },
          "chartDataValues": {
            "type": "array",
            "description": "The data series of the chart.",
            "items": {
              "type": "number"
            }
          },
          "value": {
            "type": "number",
            "description": "Progress value for gauge and line progress bar layers, usually `0` to `100`."
          },
          "maxValue": {
            "type": "number",
            "description": "Total number of ticks of a radial tick gauge, e.g. `24`, `60` or `100`."
          },
          "activeColor": {
            "type": "string",
            "description": "Color of the filled ticks of a radial tick gauge."
          },
          "inactiveColor": {
            "type": "string",
            "description": "Color of the empty ticks of a radial tick gauge."
          },
          "circleColor": {
            "type": "string",
            "description": "Color of the background ring of a simple gauge."
          },
          "progressColor": {
            "type": "string",
            "description": "Color of the progress arc or of the filled part of a line progress bar."
          },
          "textColor": {
            "type": "string",
            "description": "Color of the value label inside a simple gauge."
          },
          "remainingColor": {
            "type": "string",
            "description": "Color of the unfilled track of a line progress bar."
          },
          "label": {
            "type": "string",
            "description": "Overrides the label text of a line progress bar."
          },
          "labelAlign": {
            "type": "string",
            "description": "Horizontal alignment of the line progress bar label.",
            "enum": [
              "left",
              "center",
              "right"
            ]
          }
        }
      },
      "ReferenceImage": {
        "type": "object",
        "required": [
          "name",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Short name used in the prompt as `[name]`, e.g. `logo`."
          },
          "url": {
            "type": "string",
            "description": "Public URL of the reference image.",
            "format": "uri"
          }
        }
      },
      "PageOverride": {
        "type": "object",
        "required": [
          "index"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "description": "The page of the template this applies to, starting at 0."
          },
          "layers": {
            "type": "array",
            "description": "New values for the layers of this page.",
            "items": {
              "$ref": "#/components/schemas/LayerOverride"
            }
          }
        }
      },
      "GenerateImageRequest": {
        "type": "object",
        "description": "Every property is optional. An empty body renders the template as designed.",
        "properties": {
          "format": {
            "type": "string",
            "description": "Output format. `pdf` is for multipage templates and produces one file with every page.",
            "enum": [
              "png",
              "jpeg",
              "webp",
              "avif",
              "pdf",
              "pptx"
            ]
          },
          "metadata": {
            "type": "string",
            "description": "Custom data stored on the generated image and returned in responses and webhooks."
          },
          "params": {
            "type": "array",
            "description": "New values for the layers of a single-page template.",
            "items": {
              "$ref": "#/components/schemas/LayerOverride"
            }
          },
          "pages": {
            "type": "array",
            "description": "New values for the pages of a multipage template, instead of `params`.",
            "items": {
              "$ref": "#/components/schemas/PageOverride"
            }
          },
          "responseFormat": {
            "type": "string",
            "description": "`binary` returns the image bytes instead of JSON. Single-page image designs only.",
            "enum": [
              "json",
              "binary"
            ],
            "default": "json"
          }
        }
      },
      "GeneratedImage": {
        "type": "object",
        "description": "A generated image, or a PDF when the format is `pdf`.",
        "required": [
          "id",
          "imageUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the generated image."
          },
          "templateId": {
            "type": "string",
            "description": "The ID of the template it was generated from."
          },
          "templateName": {
            "type": "string",
            "description": "The name of that template."
          },
          "imageUrl": {
            "type": "string",
            "description": "URL of the generated image, or of the PDF when the format is `pdf`.",
            "format": "uri"
          },
          "thumbnailUrl": {
            "type": "string",
            "description": "Thumbnail URL, fitting a 300x300px square.",
            "format": "uri"
          },
          "retinaThumbnailUrl": {
            "type": "string",
            "description": "Thumbnail URL for retina displays, fitting a 600x600px square.",
            "format": "uri"
          },
          "metadata": {
            "type": "string",
            "description": "The custom metadata sent with the request."
          },
          "width": {
            "type": "integer",
            "description": "Image width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "Image height in pixels."
          }
        }
      },
      "GeneratedPage": {
        "type": "object",
        "required": [
          "id",
          "imageUrl",
          "pageIndex"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the generated page image."
          },
          "imageUrl": {
            "type": "string",
            "description": "URL of the page image.",
            "format": "uri"
          },
          "thumbnailUrl": {
            "type": "string",
            "description": "Thumbnail URL of the page image.",
            "format": "uri"
          },
          "metadata": {
            "type": "string",
            "description": "The custom metadata sent with the request."
          },
          "width": {
            "type": "integer",
            "description": "Image width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "Image height in pixels."
          },
          "pageIndex": {
            "type": "integer",
            "description": "The page of the template, starting at 0."
          }
        }
      },
      "MultipageResult": {
        "type": "object",
        "required": [
          "templateId",
          "pages"
        ],
        "properties": {
          "templateId": {
            "type": "string",
            "description": "The ID of the template."
          },
          "templateName": {
            "type": "string",
            "description": "The name of the template."
          },
          "pages": {
            "type": "array",
            "description": "One entry per generated page.",
            "items": {
              "$ref": "#/components/schemas/GeneratedPage"
            }
          }
        }
      },
      "BatchPage": {
        "type": "object",
        "properties": {
          "templateId": {
            "type": "string",
            "description": "The template for this page. Falls back to the top-level `templateId`."
          },
          "metadata": {
            "type": "string",
            "description": "Custom metadata for this page."
          },
          "layers": {
            "type": "array",
            "description": "New values for the layers of this page.",
            "items": {
              "$ref": "#/components/schemas/LayerOverride"
            }
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "pages"
        ],
        "properties": {
          "templateId": {
            "type": "string",
            "description": "The template for the pages. May be left out only if every page has its own."
          },
          "format": {
            "type": "string",
            "description": "Only `pdf` is supported.",
            "enum": [
              "pdf"
            ],
            "default": "pdf"
          },
          "metadata": {
            "type": "string",
            "description": "Custom metadata stored for each page."
          },
          "pages": {
            "type": "array",
            "description": "The pages of the PDF, in order.",
            "items": {
              "$ref": "#/components/schemas/BatchPage"
            }
          }
        }
      },
      "ImageSearchResult": {
        "type": "object",
        "required": [
          "id",
          "imageUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the generated image."
          },
          "templateId": {
            "type": "string",
            "description": "The ID of the template."
          },
          "imageUrl": {
            "type": "string",
            "description": "URL of the generated image.",
            "format": "uri"
          },
          "thumbnailUrl": {
            "type": "string",
            "description": "Thumbnail URL of the generated image.",
            "format": "uri"
          },
          "metadata": {
            "type": [
              "string",
              "null"
            ],
            "description": "The custom metadata stored on the image."
          },
          "params": {
            "type": [
              "string",
              "null"
            ],
            "description": "The parameters the image was generated with."
          },
          "width": {
            "type": "integer",
            "description": "Image width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "Image height in pixels."
          },
          "date": {
            "type": "string",
            "description": "When the image was generated.",
            "format": "date-time"
          }
        }
      },
      "AiGenerateRequest": {
        "type": "object",
        "required": [
          "modelId",
          "prompt"
        ],
        "properties": {
          "modelId": {
            "type": "string",
            "description": "The AI model. The API reference lists every model with its credit cost, e.g. `flux-2-pro`, `gemini-3.1-flash-image` or `gpt-image-2-med`."
          },
          "prompt": {
            "type": "string",
            "description": "What to generate. May contain `[name]` tokens that refer to `referenceImages`."
          },
          "ratio": {
            "type": "string",
            "description": "The aspect ratio. Supported ratios vary by model.",
            "enum": [
              "1:1",
              "3:2",
              "2:3",
              "16:9",
              "9:16",
              "4:3",
              "3:4"
            ],
            "default": "1:1"
          },
          "sourceImage": {
            "type": "string",
            "description": "URL of a photo to edit. Leave it out for text-to-image.",
            "format": "uri"
          },
          "referenceImages": {
            "type": "array",
            "description": "Named reference images. At most 6 input images in total, counting `sourceImage`.",
            "items": {
              "$ref": "#/components/schemas/ReferenceImage"
            }
          }
        }
      },
      "AiJobStarted": {
        "type": "object",
        "required": [
          "jobUid",
          "status"
        ],
        "properties": {
          "jobUid": {
            "type": "string",
            "description": "The ID of the job. Use it to poll for the result."
          },
          "status": {
            "type": "string",
            "description": "Always `queued` on a new job.",
            "enum": [
              "queued"
            ]
          }
        }
      },
      "AiJob": {
        "type": "object",
        "required": [
          "jobUid",
          "status"
        ],
        "properties": {
          "jobUid": {
            "type": "string",
            "description": "The ID of the job."
          },
          "status": {
            "type": "string",
            "description": "The job state. `done` and `error` are final.",
            "enum": [
              "queued",
              "running",
              "partial",
              "done",
              "error"
            ]
          },
          "partialUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "URL of an intermediate result, when the model produces one."
          },
          "resultUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "URL of the generated image, once `status` is `done`. Download it to keep it: the URL is not guaranteed to last."
          },
          "creditsCharged": {
            "type": "integer",
            "description": "The AI credits this job used."
          },
          "creditsRemaining": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The remaining AI credit balance, once the job has finished."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "What went wrong, when `status` is `error`."
          }
        }
      },
      "TemplateLayer": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "The layer type, e.g. `canvas`, `text` or `image`."
          },
          "name": {
            "type": "string",
            "description": "The layer name to use in `params`."
          },
          "width": {
            "type": "string",
            "description": "Layer width."
          },
          "height": {
            "type": "string",
            "description": "Layer height."
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "The current text of a text layer."
          }
        }
      },
      "Template": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the template. Use it as `{uid}` in `POST /designs/{uid}`."
          },
          "formId": {
            "type": "string",
            "description": "The ID of the template's form, used by the form widget."
          },
          "name": {
            "type": "string",
            "description": "The name of the template."
          },
          "thumbnail": {
            "type": "string",
            "description": "Thumbnail URL, fitting a 300x300px square.",
            "format": "uri"
          },
          "dateCreated": {
            "type": "string",
            "description": "When the template was created.",
            "format": "date-time"
          },
          "dateUpdated": {
            "type": "string",
            "description": "When the template was last changed.",
            "format": "date-time"
          },
          "layers": {
            "type": "array",
            "description": "The layers of the template.",
            "items": {
              "$ref": "#/components/schemas/TemplateLayer"
            }
          }
        }
      },
      "Workspace": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the workspace."
          },
          "name": {
            "type": "string",
            "description": "The name of the workspace."
          },
          "dateCreated": {
            "type": "string",
            "description": "When the workspace was created.",
            "format": "date-time"
          },
          "dateUpdated": {
            "type": "string",
            "description": "When the workspace was last changed.",
            "format": "date-time"
          },
          "memberRole": {
            "type": "string",
            "description": "The caller's role in this workspace."
          }
        }
      },
      "WorkspaceWrite": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the workspace."
          }
        }
      },
      "MediaAsset": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ID of the asset."
          },
          "folder": {
            "type": "boolean",
            "description": "Whether this asset is a folder rather than an image."
          },
          "mimeType": {
            "type": "string",
            "description": "The MIME type of the image."
          },
          "filename": {
            "type": "string",
            "description": "The file name."
          },
          "animated": {
            "type": "boolean",
            "description": "Whether the image is animated."
          },
          "size": {
            "type": "integer",
            "description": "The size in bytes."
          },
          "url": {
            "type": "string",
            "description": "URL of the image.",
            "format": "uri"
          },
          "thumbnailUrl": {
            "type": "string",
            "description": "URL of the thumbnail.",
            "format": "uri"
          },
          "dateCreated": {
            "type": "string",
            "description": "When the asset was created.",
            "format": "date-time"
          },
          "dateUpdated": {
            "type": "string",
            "description": "When the asset was last changed.",
            "format": "date-time"
          }
        }
      },
      "MediaAssetPage": {
        "type": "object",
        "required": [
          "page",
          "totalPages",
          "results"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "description": "The current page, starting at 1."
          },
          "totalPages": {
            "type": "integer",
            "description": "The total number of pages. A page holds 30 assets."
          },
          "results": {
            "type": "array",
            "description": "The assets on this page.",
            "items": {
              "$ref": "#/components/schemas/MediaAsset"
            }
          }
        }
      },
      "MediaUpload": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "description": "The image to upload.",
            "format": "binary"
          },
          "folderUid": {
            "type": "string",
            "description": "The folder to put it in."
          }
        }
      },
      "MediaFromUrlRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Public URL of the image. Set this or `base64`.",
            "format": "uri"
          },
          "base64": {
            "type": "string",
            "description": "The image as base64. Set this or `url`."
          },
          "mimeType": {
            "type": "string",
            "description": "The MIME type, mainly useful with `base64`."
          },
          "name": {
            "type": "string",
            "description": "A display name."
          },
          "folderUid": {
            "type": "string",
            "description": "The folder to put it in."
          }
        }
      },
      "HookRequest": {
        "type": "object",
        "required": [
          "targetUrl",
          "eventType",
          "templateId"
        ],
        "properties": {
          "targetUrl": {
            "type": "string",
            "description": "The URL that receives the notifications.",
            "format": "uri"
          },
          "eventType": {
            "type": "string",
            "description": "The event to subscribe to.",
            "enum": [
              "NEW_IMAGE"
            ]
          },
          "templateId": {
            "type": "string",
            "description": "The template to watch."
          }
        }
      },
      "Hook": {
        "type": "object",
        "required": [
          "uid",
          "eventType",
          "targetUrl"
        ],
        "properties": {
          "uid": {
            "type": "string",
            "description": "The ID of the webhook."
          },
          "eventType": {
            "type": "string",
            "description": "The subscribed event.",
            "enum": [
              "NEW_IMAGE"
            ]
          },
          "targetUrl": {
            "type": "string",
            "description": "The URL that receives the notifications.",
            "format": "uri"
          },
          "designUid": {
            "type": "string",
            "description": "The watched template."
          },
          "dateCreated": {
            "type": "string",
            "description": "When the webhook was created.",
            "format": "date-time"
          }
        }
      },
      "CreateDesignRequest": {
        "type": "object",
        "required": [
          "workspaceUid",
          "name"
        ],
        "properties": {
          "workspaceUid": {
            "type": "string",
            "description": "The workspace to create the design in."
          },
          "name": {
            "type": "string",
            "description": "The name of the design."
          },
          "width": {
            "type": "integer",
            "description": "Canvas width in pixels.",
            "default": 650
          },
          "height": {
            "type": "integer",
            "description": "Canvas height in pixels.",
            "default": 500
          }
        }
      },
      "Design": {
        "type": "object",
        "required": [
          "uid",
          "name",
          "width",
          "height"
        ],
        "properties": {
          "uid": {
            "type": "string",
            "description": "The ID of the new design."
          },
          "name": {
            "type": "string",
            "description": "The name of the design."
          },
          "width": {
            "type": "integer",
            "description": "Canvas width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "Canvas height in pixels."
          }
        }
      },
      "AddLayerRequest": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "The kind of layer to add.",
            "enum": [
              "text",
              "image",
              "ai_image",
              "rectangle",
              "circle"
            ]
          },
          "pageIndex": {
            "type": "integer",
            "description": "The page of a multipage design. Defaults to the first page."
          },
          "text": {
            "type": "string",
            "description": "The text of a text layer."
          },
          "imageUrl": {
            "type": "string",
            "description": "The image of an image layer.",
            "format": "uri"
          },
          "prompt": {
            "type": "string",
            "description": "The prompt of an AI Image layer."
          },
          "color": {
            "type": "string",
            "description": "The text color."
          },
          "backgroundColor": {
            "type": "string",
            "description": "The background color."
          },
          "fontSize": {
            "type": "integer",
            "description": "The font size in pixels."
          },
          "textAlign": {
            "type": "string",
            "description": "The text alignment."
          },
          "fontFamily": {
            "type": "string",
            "description": "The font family."
          },
          "fontWeight": {
            "type": "string",
            "description": "The font weight."
          },
          "aiModel": {
            "type": "string",
            "description": "The model of an AI Image layer."
          },
          "aiAspectRatio": {
            "type": "string",
            "description": "The aspect ratio of an AI Image layer."
          },
          "x": {
            "type": "number",
            "description": "Left position in pixels."
          },
          "y": {
            "type": "number",
            "description": "Top position in pixels."
          },
          "width": {
            "type": "number",
            "description": "Width in pixels."
          },
          "height": {
            "type": "number",
            "description": "Height in pixels."
          },
          "zIndex": {
            "type": "integer",
            "description": "The stacking order."
          }
        }
      },
      "PatchLayerRequest": {
        "type": "object",
        "description": "Only the fields you send change.",
        "properties": {
          "x": {
            "type": "number",
            "description": "Left position in pixels."
          },
          "y": {
            "type": "number",
            "description": "Top position in pixels."
          },
          "width": {
            "type": "number",
            "description": "Width in pixels."
          },
          "height": {
            "type": "number",
            "description": "Height in pixels."
          },
          "text": {
            "type": "string",
            "description": "The new text."
          },
          "styles": {
            "type": "object",
            "description": "CSS-like style properties to merge into the layer.",
            "additionalProperties": true
          }
        }
      },
      "LayerResult": {
        "type": "object",
        "required": [
          "designUid",
          "layer"
        ],
        "properties": {
          "designUid": {
            "type": "string",
            "description": "The ID of the design."
          },
          "layer": {
            "type": "object",
            "description": "The layer as stored in the design.",
            "additionalProperties": true
          }
        }
      },
      "LayerDeleted": {
        "type": "object",
        "required": [
          "designUid",
          "layerId",
          "deleted"
        ],
        "properties": {
          "designUid": {
            "type": "string",
            "description": "The ID of the design."
          },
          "layerId": {
            "type": "string",
            "description": "The ID of the deleted layer."
          },
          "deleted": {
            "type": "boolean",
            "description": "Always `true`."
          }
        }
      },
      "ReorderLayerRequest": {
        "type": "object",
        "description": "Send `op` or `zIndex`.",
        "properties": {
          "op": {
            "type": "string",
            "description": "Move the layer relative to the others. Takes precedence over `zIndex`.",
            "enum": [
              "toFront",
              "toBack",
              "up",
              "down"
            ]
          },
          "zIndex": {
            "type": "integer",
            "description": "An absolute stacking position."
          }
        }
      },
      "ResizeCanvasRequest": {
        "type": "object",
        "required": [
          "width",
          "height"
        ],
        "properties": {
          "width": {
            "type": "integer",
            "description": "The new width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "The new height in pixels."
          }
        }
      },
      "CanvasResult": {
        "type": "object",
        "required": [
          "designUid",
          "width",
          "height"
        ],
        "properties": {
          "designUid": {
            "type": "string",
            "description": "The ID of the design."
          },
          "width": {
            "type": "integer",
            "description": "The new width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "The new height in pixels."
          }
        }
      }
    }
  }
}
