{
    "openapi": "3.1.0",
    "info": {
        "title": "Dustin's Designer Den API",
        "version": "1.0.0",
        "description": "Programmatic access for external board game design tools: create projects, upload artwork, and sync components into a designer's account.\n\nFree to use. Requires an account and an OAuth token. See https://dustinsdesignerden.com/developers/api for the integration guide."
    },
    "servers": [
        {
            "url": "https://dustinsdesignerden.com/api/v1"
        }
    ],
    "components": {
        "securitySchemes": {
            "oauth2": {
                "type": "oauth2",
                "description": "Authorization code with PKCE. Personal access tokens use the same bearer format.",
                "flows": {
                    "authorizationCode": {
                        "authorizationUrl": "https://dustinsdesignerden.com/oauth/authorize",
                        "tokenUrl": "https://dustinsdesignerden.com/oauth/token",
                        "refreshUrl": "https://dustinsdesignerden.com/oauth/token",
                        "scopes": {
                            "projects:read": "See your projects and their details",
                            "projects:write": "Create new projects, and edit the details of your existing ones, including the description shown publicly",
                            "assets:read": "See the files stored in your projects",
                            "assets:write": "Upload and delete files in your storage",
                            "components:read": "See the components in your projects, how they are grouped into decks and stacks, and your custom mask shapes",
                            "components:write": "Create and update components, decks and stacks, and delete the ones it created",
                            "webhooks:write": "Get notified when your projects or components change"
                        }
                    }
                }
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "string",
                                "example": "project_locked"
                            },
                            "message": {
                                "type": "string"
                            },
                            "details": {
                                "type": "object",
                                "additionalProperties": true
                            }
                        }
                    }
                }
            },
            "ComponentRow": {
                "type": "object",
                "required": [
                    "name"
                ],
                "description": "One component. `unique_id` is the sync key: send your own stable id and later calls update in place instead of duplicating.",
                "properties": {
                    "unique_id": {
                        "type": "string",
                        "description": "Your stable id for this component. Strongly recommended."
                    },
                    "name": {
                        "type": "string"
                    },
                    "quantity": {
                        "type": "integer",
                        "minimum": 1
                    },
                    "description": {
                        "type": "string"
                    },
                    "notes": {
                        "type": "string"
                    },
                    "image_url": {
                        "type": "string",
                        "format": "uri"
                    },
                    "back_image_url": {
                        "type": "string",
                        "format": "uri"
                    },
                    "card_mask_type": {
                        "type": "string",
                        "description": "Card shape. A `value` from GET /masks, or one of the designer's custom mask names."
                    },
                    "shape": {
                        "type": "string",
                        "description": "Token shape, same value space as card_mask_type."
                    },
                    "color": {
                        "type": "string"
                    },
                    "color_back": {
                        "type": "string"
                    },
                    "deck_names": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Cards only. Names of the decks this card belongs to. Created if missing. A pipe-delimited string is also accepted."
                    },
                    "stack_names": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Tokens only. Names of the stacks this token belongs to."
                    },
                    "phys_width_in": {
                        "type": "number"
                    },
                    "phys_height_in": {
                        "type": "number"
                    },
                    "sort_order": {
                        "type": "integer"
                    }
                }
            },
            "BatchResult": {
                "type": "object",
                "properties": {
                    "created": {
                        "type": "integer"
                    },
                    "updated": {
                        "type": "integer"
                    },
                    "deleted": {
                        "type": "integer"
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Per-row problems. Rows that succeeded still applied."
                    },
                    "kept_artwork": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Components the batch matched but sent no artwork for, so they kept what they had."
                    },
                    "components": {
                        "type": "array",
                        "items": {
                            "type": "object"
                        }
                    },
                    "internalizing": {
                        "type": "boolean"
                    }
                }
            }
        }
    },
    "x-webhook-events": {
        "components.changed": "Components were created, updated or deleted in a project",
        "project.updated": "A project's details were changed"
    },
    "x-component-types": [
        "card",
        "token",
        "dice",
        "board",
        "rulebook",
        "spinner",
        "coin",
        "bag",
        "counter",
        "timer"
    ],
    "x-rate-limits": {
        "reads": 300,
        "component_writes": 60,
        "asset_uploads": 30,
        "project_creation_per_hour": 10,
        "global_per_hour": 1000
    },
    "paths": {
        "/masks": {
            "get": {
                "operationId": "masks.index",
                "x-route-name": "api.v1.masks.index",
                "tags": [
                    "Account"
                ],
                "summary": "Every shape a component can take, including the designer's custom masks",
                "security": [
                    {
                        "oauth2": [
                            "components:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Returns `card` and `token` arrays. Send an option's `value` verbatim as `card_mask_type` (cards) or `shape` (tokens).\n\nEach option has a `source`: `preset` for a shape the app ships, or `custom` for one this designer drew in the Mask Editor. Custom masks vary per account, so the list cannot be hardcoded.\n\nEvery option carries exactly ONE ratio, `aspect`, written the way ratios are written: `1:1` square, `5:7` a portrait card, `7:5` landscape. It is the ratio the shape was authored at, not a decimal of it, so it is the same value the designer typed in the Mask Editor. `null` means the shape forces no footprint and the component keeps its own physical size. A shaped mask has to force its box, because a hexagon squeezed into a 2.5x3.5 card box is not a hexagon.\n\nCall this before offering a shape picker. You may also send a custom mask's NAME as the value on a component write and it will be resolved for you.\n\nA shape that is not in this list and is not one of the designer's own mask names is REJECTED with `422`, rather than stored. A value the app does not recognise would come back describing a shape that appears nowhere here, and the next time the designer opened that component the shape dropdown would match nothing, so saving the form would clear the mask.",
                "x-required-scopes": [
                    "components:read"
                ]
            }
        },
        "/me": {
            "get": {
                "operationId": "me",
                "x-route-name": "api.v1.me",
                "tags": [
                    "Account"
                ],
                "summary": "Who this token belongs to, plus quota and plan limits",
                "security": [
                    {
                        "oauth2": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Who the token acts for, what it is allowed to do, and how much room the designer has left. A good first call.\n\nWorth reading before an upload or creating a project, so you can say they are out of space or at their project cap instead of failing halfway through. It also reports the scopes the token actually holds, which can be fewer than you asked for, and the `app` value your writes get stamped with, which is the same value that decides which rows your deletes can reach.",
                "x-any-of-scopes": [
                    "projects:read",
                    "projects:write",
                    "assets:read",
                    "assets:write",
                    "components:read",
                    "components:write",
                    "webhooks:write"
                ]
            }
        },
        "/projects": {
            "get": {
                "operationId": "projects.index",
                "x-route-name": "api.v1.projects.index",
                "tags": [
                    "Projects"
                ],
                "summary": "List the projects this token can reach",
                "security": [
                    {
                        "oauth2": [
                            "projects:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Every project this designer owns, newest first, paginated. Each entry carries `component_count`, so you can show a picker without a second call per project.\n\nThis is normally your first call after `/me`: a designer picks which of their games your tool should work on, and the `id` you get back is the `{project}` in almost every other path on this page.",
                "parameters": [
                    {
                        "name": "per_page",
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "maximum": 200
                        },
                        "in": "query",
                        "required": false
                    },
                    {
                        "name": "page",
                        "schema": {
                            "type": "integer"
                        },
                        "in": "query",
                        "required": false
                    }
                ],
                "x-required-scopes": [
                    "projects:read"
                ]
            },
            "post": {
                "operationId": "projects.store",
                "x-route-name": "api.v1.projects.store",
                "tags": [
                    "Projects"
                ],
                "summary": "Create a project",
                "security": [
                    {
                        "oauth2": [
                            "projects:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Subject to the designer's plan limit. A free account is capped, and exceeding it returns 403 `plan_limit_reached`. Surface that message rather than swallowing it.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "name": "Harvest Moon",
                                "short_description": "A farming game."
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "projects:write"
                ]
            }
        },
        "/projects/{project}": {
            "get": {
                "operationId": "projects.show",
                "x-route-name": "api.v1.projects.show",
                "tags": [
                    "Projects"
                ],
                "summary": "Read one project",
                "security": [
                    {
                        "oauth2": [
                            "projects:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "One project, with its component count. Use it to refresh something you already know about, for instance to check `is_locked` before you start writing.\n\nA locked project rejects every write with `409 project_locked`, so looking first lets you tell the designer why, instead of failing halfway through a sync.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "x-required-scopes": [
                    "projects:read"
                ]
            },
            "patch": {
                "operationId": "projects.update",
                "x-route-name": "api.v1.projects.update",
                "tags": [
                    "Projects"
                ],
                "summary": "Update a project's details",
                "security": [
                    {
                        "oauth2": [
                            "projects:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Updates the project's own details, such as its name or description. Send only the fields you want changed; anything you leave out is untouched.\n\nThis never affects components. Deliberately narrow: it cannot alter ownership, lock state or billing. If at least one field really changes, a `project.updated` webhook fires naming the fields that were written.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "genre": "Worker Placement",
                                "playtime_minutes": 45
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "projects:write"
                ]
            }
        },
        "/projects/{project}/assets": {
            "get": {
                "operationId": "assets.index",
                "x-route-name": "api.v1.assets.index",
                "tags": [
                    "Assets"
                ],
                "summary": "List files stored for this project",
                "security": [
                    {
                        "oauth2": [
                            "assets:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Everything in this project's storage folder: a `key`, a public `url` and a size for each file.\n\nThe `key` is what you pass to the delete endpoint. The `url` is what you put in a component's `image_url`. These files count against the designer's storage quota, so `/me` is worth reading before a large upload.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "x-required-scopes": [
                    "assets:read"
                ]
            },
            "post": {
                "operationId": "assets.store",
                "x-route-name": "api.v1.assets.store",
                "tags": [
                    "Assets"
                ],
                "summary": "Upload artwork (multipart)",
                "security": [
                    {
                        "oauth2": [
                            "assets:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Field name is `file`. Images only (jpeg, png, gif, webp, avif, svg), 50 MB max. Optional `convert_to_webp` / `convert_to_avif` booleans. Counts against the designer's storage quota.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "file"
                                ],
                                "properties": {
                                    "file": {
                                        "type": "string",
                                        "format": "binary"
                                    },
                                    "convert_to_webp": {
                                        "type": "boolean"
                                    },
                                    "convert_to_avif": {
                                        "type": "boolean"
                                    }
                                }
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "assets:write"
                ]
            },
            "delete": {
                "operationId": "assets.destroy",
                "x-route-name": "api.v1.assets.destroy",
                "tags": [
                    "Assets"
                ],
                "summary": "Delete a file",
                "security": [
                    {
                        "oauth2": [
                            "assets:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Removes one stored file and frees the space against the designer's quota. Address it with the `key` from the asset listing.\n\nComponents pointing at that URL are not changed, so deleting a file that is in use leaves them with a broken image. Repoint or remove those components first. Only keys inside this project's folder can be reached: not the designer's avatar, and not another project's artwork.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "key": "users/{slug}/projects/42/dagger.png"
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "assets:write"
                ]
            }
        },
        "/projects/{project}/assets/from-url": {
            "post": {
                "operationId": "assets.storeFromUrl",
                "x-route-name": "api.v1.assets.store-from-url",
                "tags": [
                    "Assets"
                ],
                "summary": "Mirror artwork you already host",
                "security": [
                    {
                        "oauth2": [
                            "assets:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "The server fetches the URL and stores a copy in the designer's own storage. Public http(s) only; private, loopback and link-local addresses are refused.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "url": "https://cdn.example.com/dagger.png"
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "assets:write"
                ]
            }
        },
        "/projects/{project}/components": {
            "get": {
                "operationId": "components.index",
                "x-route-name": "api.v1.components.index",
                "tags": [
                    "Components"
                ],
                "summary": "List components",
                "security": [
                    {
                        "oauth2": [
                            "components:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Every component in the project, paginated, grouped by type and then in the designer's own ordering.\n\nRead this before writing, to see what already exists and to pick up the `unique_id` values of rows you did not create yourself. Narrow it to one type with `?type=card`.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "type",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "card",
                                "token",
                                "dice",
                                "board",
                                "rulebook",
                                "spinner",
                                "coin",
                                "bag",
                                "counter",
                                "timer",
                                "packaging",
                                "other",
                                "piece",
                                "cube"
                            ]
                        },
                        "description": "Accepts the legacy types too (other, piece, cube), so you can list older rows in order to migrate them. You cannot create those.",
                        "in": "query",
                        "required": false
                    },
                    {
                        "name": "per_page",
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "maximum": 200
                        },
                        "in": "query",
                        "required": false
                    }
                ],
                "x-required-scopes": [
                    "components:read"
                ]
            },
            "post": {
                "operationId": "components.store",
                "x-route-name": "api.v1.components.store",
                "tags": [
                    "Components"
                ],
                "summary": "Create or update a single component",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Creates or updates ONE component. It is a batch of one and behaves identically to the batch endpoint, so anything true there is true here.\n\nMatching is on `unique_id`: send your own stable id, and the first call creates while every later call with that id updates in place. Reach for this when a single thing changed and a whole batch would be noise. For a real sync, prefer the batch endpoint, which is one transaction and one webhook instead of many.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ComponentRow"
                            },
                            "example": {
                                "type": "card",
                                "component": {
                                    "unique_id": "ext-1",
                                    "name": "Rusty Dagger"
                                }
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "components:write"
                ]
            },
            "delete": {
                "operationId": "components.destroyAll",
                "x-route-name": "api.v1.components.destroy-all",
                "tags": [
                    "Components"
                ],
                "summary": "Delete every component this app created",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Never a full wipe. Scoped to this app's own rows; hand-made and other apps' components are untouched. Optional `type` narrows it further.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "type",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "card",
                                "token",
                                "dice",
                                "board",
                                "rulebook",
                                "spinner",
                                "coin",
                                "bag",
                                "counter",
                                "timer",
                                "packaging",
                                "other",
                                "piece",
                                "cube"
                            ]
                        },
                        "in": "query",
                        "required": false
                    }
                ],
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/projects/{project}/components/batch": {
            "post": {
                "operationId": "components.batch",
                "x-route-name": "api.v1.components.batch",
                "tags": [
                    "Components"
                ],
                "summary": "Create or update components in bulk (the primary write path)",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Send up to 500 rows. `unique_id` is the sync key: the first call creates, later calls with the same ids update in place.\n\nOptional flags: `match_by_name` (match on name when ids differ), `remove_missing` (delete rows THIS app created that the batch omitted), `internalize_images` (copy external artwork into the designer's storage, which spends their quota, off by default).\n\nThe whole batch is one transaction; per-row problems come back in `errors` while valid rows still apply.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/BatchResult"
                            },
                            "example": {
                                "type": "card",
                                "remove_missing": false,
                                "components": [
                                    {
                                        "unique_id": "ext-tool-card-001",
                                        "name": "Rusty Dagger",
                                        "quantity": 3,
                                        "image_url": "https://cdn.example.com/dagger-front.png",
                                        "card_mask_type": "rectangle_rounded",
                                        "deck_names": "Weapons|Starter Deck"
                                    }
                                ]
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/projects/{project}/components/{uniqueId}": {
            "delete": {
                "operationId": "components.destroy",
                "x-route-name": "api.v1.components.destroy",
                "tags": [
                    "Components"
                ],
                "summary": "Delete one component",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Deletes one component, addressed by its `unique_id`.\n\nOnly rows your own app created. A component the designer made by hand, or one another integration created, returns `403 forbidden`. Deletes are scoped by the same `source` stamp your writes carry, so one tool can never remove another's work.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "uniqueId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The component's `unique_id`. This endpoint takes the unique_id only, never our numeric `id`."
                    }
                ],
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/projects/{project}/groups": {
            "get": {
                "operationId": "groups.index",
                "x-route-name": "api.v1.groups.index",
                "tags": [
                    "Groups"
                ],
                "summary": "List decks and stacks",
                "security": [
                    {
                        "oauth2": [
                            "components:read"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Every deck and stack in the project. A deck holds cards, a stack holds tokens.\n\nContents are left out unless you ask for them, because a project with several large decks would return a very big payload on a plain listing. Add `?include_members=1` when you actually need the components and their order.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "kind",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "deck",
                                "stack"
                            ]
                        },
                        "in": "query",
                        "required": false
                    },
                    {
                        "name": "include_members",
                        "schema": {
                            "type": "boolean"
                        },
                        "description": "Include each group's components in order. Off by default because large decks make big payloads.",
                        "in": "query",
                        "required": false
                    }
                ],
                "x-required-scopes": [
                    "components:read"
                ]
            },
            "post": {
                "operationId": "groups.store",
                "x-route-name": "api.v1.groups.store",
                "tags": [
                    "Groups"
                ],
                "summary": "Create a deck or stack",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "`kind` is `deck` (holds cards) or `stack` (holds tokens). Names are matched case-insensitively within a project, so creating one that already exists returns the existing group with `200` instead of making a duplicate.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "kind": "deck",
                                "name": "Weapons"
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/projects/{project}/groups/{group}": {
            "patch": {
                "operationId": "groups.update",
                "x-route-name": "api.v1.groups.update",
                "tags": [
                    "Groups"
                ],
                "summary": "Rename or reorder a deck or stack",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Renames a deck or stack, or moves it in the project's ordering. The contents are untouched: use the members endpoint for those.\n\n`kind` cannot be changed. It decides which component type the group may hold, so turning a deck into a stack would strand every card already in it. Names are matched case-insensitively within a project, so renaming onto a name already in use is rejected.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "group",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Group id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "name": "Starter Weapons",
                                "position": 2
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "components:write"
                ]
            },
            "delete": {
                "operationId": "groups.destroy",
                "x-route-name": "api.v1.groups.destroy",
                "tags": [
                    "Groups"
                ],
                "summary": "Delete a deck or stack",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Deletes the deck or stack itself. The components that were in it are NOT deleted, they stay in the project as loose components, which is why the response confirms `components_retained`.\n\nUse this when a grouping was a mistake. To empty a deck but keep it, send an empty `members` array instead.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "group",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Group id."
                    }
                ],
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/projects/{project}/groups/{group}/members": {
            "put": {
                "operationId": "groups.members",
                "x-route-name": "api.v1.groups.members",
                "tags": [
                    "Groups"
                ],
                "summary": "Set a deck or stack's contents and card order",
                "security": [
                    {
                        "oauth2": [
                            "components:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Replaces the membership wholesale. **Array order becomes card order**, so this is how you control the order of a deck.\n\nIdentify each member by its `unique_id` \u2014 the only id this API accepts for a member, so you can push a deck using only your own ids. Ids are looked up inside this project only. Responses also return our internal `component_id` for reference; it is not accepted here. Optional per-member `count` (how many copies) and `face_up` (`true` front up, `false` face down; omit it to let the entry be dealt however the group is).\n\nSend an empty array to empty the deck without deleting any component.\n\nTwo rules are enforced, the same ones the in-app Deck & Stack Builder enforces: a deck holds only cards and a stack only tokens, and **every member must share one shape/mask**. A mixed deck is rejected with `400` rather than silently dropping cards, because it renders wrong on the tabletop.",
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Project id."
                    },
                    {
                        "name": "group",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Group id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "members": [
                                    {
                                        "unique_id": "ext-card-001",
                                        "count": 2
                                    },
                                    {
                                        "unique_id": "ext-card-002"
                                    },
                                    {
                                        "unique_id": "ext-card-003",
                                        "face_up": false
                                    }
                                ]
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "components:write"
                ]
            }
        },
        "/webhooks": {
            "get": {
                "operationId": "webhooks.index",
                "x-route-name": "api.v1.webhooks.index",
                "tags": [
                    "Webhooks"
                ],
                "summary": "List the webhooks this app registered",
                "security": [
                    {
                        "oauth2": [
                            "webhooks:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Only the endpoints YOUR app registered for this designer. Ones belonging to other apps are never visible.\n\nEach entry reports `is_active`, `last_delivered_at`, `last_status` and `consecutive_failures`, which is enough to show an integration-health panel, or to notice that an endpoint was switched off after repeated failures and needs registering again.",
                "x-required-scopes": [
                    "webhooks:write"
                ]
            },
            "post": {
                "operationId": "webhooks.store",
                "x-route-name": "api.v1.webhooks.store",
                "tags": [
                    "Webhooks"
                ],
                "summary": "Register a webhook",
                "security": [
                    {
                        "oauth2": [
                            "webhooks:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Returns `secret` exactly once. Store it, you cannot read it back. Sign checks use `X-DDD-Signature: sha256=HMAC_SHA256(\"{timestamp}.{body}\", secret)` with the timestamp from `X-DDD-Timestamp`.\n\nEvents: components.changed, project.updated. Public http(s) URLs only. Max 10 per app per account.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "example": {
                                "url": "https://yourtool.example.com/hooks/ddd",
                                "events": [
                                    "components.changed"
                                ]
                            }
                        }
                    }
                },
                "x-required-scopes": [
                    "webhooks:write"
                ]
            }
        },
        "/webhooks/{webhook}": {
            "delete": {
                "operationId": "webhooks.destroy",
                "x-route-name": "api.v1.webhooks.destroy",
                "tags": [
                    "Webhooks"
                ],
                "summary": "Delete a webhook",
                "security": [
                    {
                        "oauth2": [
                            "webhooks:write"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Missing, malformed, expired or revoked token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Missing required scope, or a plan limit was reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found, or not owned by this token",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The project is locked and refuses writes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed, or the storage quota would be exceeded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limited; see the Retry-After header",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Stops deliveries and removes the registration. Anything still queued is dropped too, because every delivery re-checks that its webhook exists before sending.\n\nThere is no way to edit a webhook. To change its URL or its events, delete it and register again, which also issues a fresh secret.",
                "parameters": [
                    {
                        "name": "webhook",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Webhook id."
                    }
                ],
                "x-required-scopes": [
                    "webhooks:write"
                ]
            }
        }
    }
}