{
  "openapi": "3.0.1",
  "info": {
    "title": "InfoSlides Agent API",
    "description": "The InfoSlides Agent API is a REST surface for digital signage: create a workspace,\nupload or generate slides, register a TV or display, assign a schedule, and read back a\nlive HLS stream link. It is the API behind the InfoSlides CLI and MCP server, and it is\ndesigned to be driven by AI agents as well as by conventional clients.\n\n**Success envelope.** Every JSON response wraps its payload in `data`, with optional\n`warnings`: `{ \"data\": { ... }, \"warnings\": [ { \"code\": \"...\", \"message\": \"...\" } ] }`.\nTwo endpoints are deliberately not enveloped: `GET /v1/slides/{id}/preview.png` returns\nraw `image/png` bytes, and the CLI auth legs return HTTP redirects.\n\n**Error envelope.** Errors return `{ \"error\": { \"code\": \"...\", \"message\": \"...\",\n\"details\": { ... } } }`. Validation failures populate `details.fields`, a map of field\nname to an array of messages.\n\n**Authentication.** Send an API key as a bearer token: `Authorization: Bearer isk_admin_…`.\nTwo key formats exist. `isk_admin_` keys are full tenant-admin credentials. `isk_dp_`\nkeys are push-only data-provider keys, restricted by middleware to exactly one request\nshape — `POST /v1/slides/{id}/source` for a slide the key is bound to; every other route\nis rejected with `KeyScopeViolation` before routing. A tenant JWT session obtained\nthrough the CLI login flow works anywhere an admin key does.\n\n`POST /v1/tenants` is the single anonymous endpoint: it provisions a brand-new workspace\non the permanent free plan and returns its admin API key exactly once. Nothing else on\n`/v1` is reachable without a credential.\n\n**Idempotency.** Creating endpoints accept an `Idempotency-Key` request header (up to\n128 characters). The first successful response for a key is stored for 24 hours; a repeat\nrequest with the same key returns that stored response verbatim, with\n`Idempotent-Replay: true`, without re-executing the action.\n\n## Error codes\n\n| Code | HTTP | Meaning |\n| --- | --- | --- |\n| `Unauthorized` | 401 | Missing credential, or a credential type that is not an API key. |\n| `InvalidApiKey` | 401 | The presented API key does not match any known key. |\n| `ApiKeyRevoked` | 401 | The presented API key exists but has been revoked. |\n| `Forbidden` | 403 | Valid credential, insufficient rights. |\n| `EmailNotVerified` | 403 | Workspace owner's email is unverified. Remediate with POST /v1/auth/resend-verification. |\n| `EntitlementRequired` | 403 | The feature requires a paid plan. Details carry an upgrade URL. |\n| `DeviceLimitReached` | 403 | The workspace's active-device quota is exhausted. |\n| `KeyScopeViolation` | 403 | A push-only data-provider key was used outside its allowed endpoint or slides. |\n| `ValidationFailed` | 400 | Schema or field validation error. details.fields lists the offending fields. |\n| `NotFound` | 404 | The resource does not exist within this workspace. |\n| `RateLimited` | 429 | Too many requests. The Retry-After header is set. |\n| `InternalError` | 500 | Unexpected server error. |\n\n## Endpoints honouring `Idempotency-Key`\n\n`POST /v1/tenants`, `POST /v1/slideshows`, `POST /v1/slideshows/pptx`, `POST /v1/slideshows/{id}/clone`, `POST /v1/slideshows/{id}/slides`, `POST /v1/slideshows/{id}/slides/dynamic`, `POST /v1/media`, `POST /v1/templates`, `POST /v1/gallery/{id}/clone`, `POST /v1/devices`, `POST /v1/apikeys`\n",
    "version": "v1"
  },
  "paths": {
    "/v1/apikeys": {
      "post": {
        "tags": [
          "API keys"
        ],
        "summary": "Creates a workspace API key — admin (`isk_admin_`) or push-only data-provider (`isk_dp_`, bound to specific slide ids). The plaintext key is returned exactly once.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "TenantAdmin",
        "x-infoslides-idempotent": true
      },
      "get": {
        "tags": [
          "API keys"
        ],
        "summary": "Lists the workspace's keys, revoked ones included. Only key prefixes are returned, never full keys.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "TenantAdmin"
      }
    },
    "/v1/apikeys/{id}": {
      "delete": {
        "tags": [
          "API keys"
        ],
        "summary": "Revokes an API key. Idempotent — revoking an already-revoked key still succeeds.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "TenantAdmin"
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Returns a Paddle checkout URL the workspace admin can use to upgrade or change plan.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "TenantAdmin"
      }
    },
    "/v1/auth/cli/start": {
      "get": {
        "tags": [
          "CLI authentication"
        ],
        "summary": "Browser entry point. Validates the PKCE and loopback parameters, then redirects into the OAuth flow for the requested provider (`google`, `microsoft`, `github`).",
        "parameters": [
          {
            "name": "provider",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "codeChallenge",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirectUri",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "Anonymous"
      }
    },
    "/v1/auth/cli/complete": {
      "get": {
        "tags": [
          "CLI authentication"
        ],
        "summary": "Completion leg after external sign-in. Issues a single-use authorization code and redirects back to the CLI's loopback listener.",
        "parameters": [
          {
            "name": "sid",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "Anonymous"
      }
    },
    "/v1/auth/cli/exchange": {
      "post": {
        "tags": [
          "CLI authentication"
        ],
        "summary": "Exchanges the authorization code plus its PKCE verifier for a session token.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CliExchangeRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CliExchangeRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CliExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "Anonymous"
      }
    },
    "/v1/devices": {
      "post": {
        "tags": [
          "Devices and streams"
        ],
        "summary": "Registers a device (TV, display, menu board). Gated by owner email verification and the workspace's active-device quota.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateDeviceRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateDeviceRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateDeviceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      },
      "get": {
        "tags": [
          "Devices and streams"
        ],
        "summary": "Lists every device owned by the workspace, broadcast channels included.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/devices/{id}/status": {
      "get": {
        "tags": [
          "Devices and streams"
        ],
        "summary": "Returns a device's online/offline status and its currently-resolved slideshow.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/devices/{id}/schedule": {
      "post": {
        "tags": [
          "Devices and streams"
        ],
        "summary": "Assigns a single always-on slideshow to a device, replacing any existing default entry. An orientation mismatch attaches an `AspectMismatch` warning rather than failing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AssignScheduleRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AssignScheduleRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1AssignScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/devices/{id}/stream": {
      "get": {
        "tags": [
          "Devices and streams"
        ],
        "summary": "Returns the device's stable HLS stream-link URL. Always returns a URL, flagging `StreamNotReady` when nothing is playable yet.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/gallery": {
      "get": {
        "tags": [
          "Gallery"
        ],
        "summary": "Lists the curated starter-gallery decks available to clone.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "AnyCredential"
      }
    },
    "/v1/gallery/{id}/clone": {
      "post": {
        "tags": [
          "Gallery"
        ],
        "summary": "Clones a starter-gallery deck into the caller's workspace, keeping its original display name.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/media": {
      "post": {
        "tags": [
          "Media"
        ],
        "summary": "Uploads a file (`multipart/form-data`, a single `file` part) into the media library and returns its id, type, and pixel dimensions.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              },
              "encoding": {
                "file": {
                  "style": "form"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/slides/{id}/conditions": {
      "put": {
        "tags": [
          "Slides"
        ],
        "summary": "Replaces a slide's visibility conditions (time of day, weekday, data triggers). An empty list clears them.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1SetConditionsRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1SetConditionsRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1SetConditionsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/slides/{id}/source": {
      "post": {
        "tags": [
          "Slides"
        ],
        "summary": "Pushes a JSON payload onto a dynamic slide's live data, validated against the template's data schema, and queues a re-render. Supports `?dryRun=true`. The only endpoint a push-only `isk_dp_` key may call.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dryRun",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { }
            },
            "text/json": {
              "schema": { }
            },
            "application/*+json": {
              "schema": { }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "DataProvider"
      }
    },
    "/v1/slides/{id}/preview.png": {
      "get": {
        "tags": [
          "Slides"
        ],
        "summary": "Returns a PNG of the slide's current rendered state — raw `image/png` bytes, not an envelope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/slideshows/pptx": {
      "post": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Uploads a `.pptx` file (`multipart/form-data`) and creates a slideshow from it.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "title": {
                    "type": "string"
                  }
                }
              },
              "encoding": {
                "file": {
                  "style": "form"
                },
                "title": {
                  "style": "form"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/slideshows": {
      "post": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Creates a slideshow, optionally seeded with an ordered list of media-URL or template-backed slides.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateSlideshowRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateSlideshowRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateSlideshowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      },
      "get": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Lists the workspace's slideshows as a page of summaries.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/slideshows/{id}": {
      "get": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Returns a slideshow's full detail: resolution and its unified slide sequence.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      },
      "patch": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Partial update: title, resolution, and/or a full slide reorder. Any change queues a re-render.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1UpdateSlideshowRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1UpdateSlideshowRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1UpdateSlideshowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/slideshows/{id}/clone": {
      "post": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Clones a slideshow — its row, its PPTX file, and every child slide — within the workspace.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/slideshows/{id}/slides": {
      "post": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Inserts a slide from a media URL or an existing media asset id. An aspect-ratio mismatch attaches an `AspectMismatch` warning rather than failing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddMediaSlideRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddMediaSlideRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddMediaSlideRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/slideshows/{id}/slides/dynamic": {
      "post": {
        "tags": [
          "Slideshows"
        ],
        "summary": "Inserts a template-driven dynamic slide. Push its data with `POST /v1/slides/{id}/source`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddDynamicSlideRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddDynamicSlideRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1AddDynamicSlideRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/templates": {
      "post": {
        "tags": [
          "Templates"
        ],
        "summary": "Creates a workspace-owned template from code mode (`html` + `css`) or AI mode (`prompt` + `sampleJson`). Supports `?dryRun=true`. Requires a plan that includes AI Studio.",
        "parameters": [
          {
            "name": "dryRun",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateTemplateRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateTemplateRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateTemplateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager",
        "x-infoslides-idempotent": true
      },
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Lists templates visible to the workspace — global templates plus its own.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/templates/{id}": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Returns a single template visible to the workspace.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "ContentManager"
      }
    },
    "/v1/auth/resend-verification": {
      "post": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Resends the workspace owner's verification email. Idempotent and silent — always reports success.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "AnyCredential"
      }
    },
    "/v1/tenants": {
      "post": {
        "tags": [
          "Workspaces"
        ],
        "summary": "Provisions a brand-new workspace on the free plan and returns its admin API key exactly once. Accepts an optional `source` field (`web`, `api`, `cli`, `mcp`) recording how the signup originated.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Client-generated key (max 128 chars). Repeating a request with the same key within 24 hours replays the original response and sets `Idempotent-Replay: true` instead of re-executing.",
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTenantRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTenantRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTenantRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "Anonymous",
        "x-infoslides-idempotent": true
      }
    },
    "/v1/tenant": {
      "get": {
        "tags": [
          "Workspaces"
        ],
        "summary": "\"Whoami\": the caller's workspace identity, plan, device-quota usage, and the scope of the credential used.",
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "x-infoslides-auth": "AnyCredential"
      }
    }
  },
  "components": {
    "schemas": {
      "CliExchangeRequest": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "codeVerifier": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "slideIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateTenantRequest": {
        "type": "object",
        "properties": {
          "tenantName": {
            "type": "string",
            "nullable": true
          },
          "ownerEmail": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1AddDynamicSlideRequest": {
        "type": "object",
        "properties": {
          "templateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "durationSeconds": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "position": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1AddMediaSlideRequest": {
        "type": "object",
        "properties": {
          "mediaUrl": {
            "type": "string",
            "nullable": true
          },
          "mediaAssetId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "durationSeconds": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "position": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1AssignScheduleRequest": {
        "type": "object",
        "properties": {
          "slideshowIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1CreateDeviceRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "resolution": {
            "$ref": "#/components/schemas/V1Resolution"
          }
        },
        "additionalProperties": false
      },
      "V1CreateSlideshowRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "resolution": {
            "$ref": "#/components/schemas/V1Resolution"
          },
          "slides": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1NewSlide"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1CreateTemplateRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "prompt": {
            "type": "string",
            "nullable": true
          },
          "sampleJson": {
            "nullable": true
          },
          "html": {
            "type": "string",
            "nullable": true
          },
          "css": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1NewSlide": {
        "type": "object",
        "properties": {
          "mediaUrl": {
            "type": "string",
            "nullable": true
          },
          "templateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "durationSeconds": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1Resolution": {
        "type": "object",
        "properties": {
          "width": {
            "type": "integer",
            "format": "int32"
          },
          "height": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "V1SetConditionsRequest": {
        "type": "object",
        "properties": {
          "conditions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1SlideCondition"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1SlideCondition": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "V1UpdateSlideshowRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "resolution": {
            "$ref": "#/components/schemas/V1Resolution"
          },
          "slideOrder": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "description": "Workspace API key sent as a bearer token. `isk_admin_…` keys are full workspace-admin credentials; `isk_dp_…` keys are push-only and may call only POST /v1/slides/{id}/source for the slides they are bound to. POST /v1/tenants is the one anonymous endpoint and needs no credential.",
        "scheme": "bearer"
      }
    }
  }
}