{
  "openapi": "3.1.0",
  "info": {
    "title": "Taste Engine Design API",
    "version": "1.0.0",
    "description": "Public REST API for the Taste Engine. Submit a URL, extract a structured design system from any website, search the brand corpus by aesthetic, and ground prompts in real brand profiles.\n\nAll requests are authenticated with an API key sent in the `X-API-Key` header. Create one directly in the Engine dashboard at https://engine.tastelabs.com/app/api-keys.",
    "contact": {
      "name": "Taste Engine Support",
      "email": "support@thetaste.ai"
    }
  },
  "servers": [
    {
      "url": "https://api.tastelabs.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Identity of the authenticated API key holder."
    },
    {
      "name": "Submissions",
      "description": "Create and track extraction jobs, then retrieve the resulting design system."
    },
    {
      "name": "Prompts",
      "description": "Rewrite prompts grounded in an extracted brand profile."
    },
    {
      "name": "Search",
      "description": "Find brands in the curated corpus by describing an aesthetic, or by similarity to one of your own extractions."
    },
    {
      "name": "Brand adherence",
      "description": "Judge how well a page adheres to a reference site's brand: submit a pair of URLs, poll the job, then read the verdict."
    }
  ],
  "paths": {
    "/design/me": {
      "get": {
        "operationId": "getCurrentUser",
        "summary": "Get current user",
        "description": "Returns the identity and role associated with the authenticated API key.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "The authenticated identity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "example": {
                  "user_id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "email": "you@example.com",
                  "role": "user"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions": {
      "post": {
        "operationId": "createSubmission",
        "summary": "Create submission",
        "description": "Creates an extraction job for a single URL. The job runs asynchronously: poll `GET /submissions/{id}/result` until the status is `completed` or `failed`.\n\nSet the `map` query parameter to `true` to run map-mode discovery, which crawls the site and submits multiple URLs (up to `max_urls`).\n\nPass a nonempty sections array in the JSON body to extract only selected parts. Omit it or send null for a full extraction. Selection is supported for single URLs and cannot be combined with map=true or enable_deep_analysis=true.\n\nSelective results are saved for retrieval but never reused as cache sources. A recent full cached extraction can satisfy a selective request; only requested sections are returned. Set force=true to bypass cache reuse.\n\nIf explicitly requested assets/icons extraction fails after retries, the job fails and its credits are refunded. Successfully finding no assets/icons is a valid result.\n\nFresh full extractions include brand_assets by default. Pass sections=[\"brand_assets\"] to extract only brand imagery. Existing saved results are not updated automatically; set force=true to collect fresh assets. Brand asset processing failure can leave this section null on a full extraction, but fails a fresh selective submission that requests it. Finding no images is a successful empty result. See [Brand assets](/concepts/brand-assets).",
        "tags": [
          "Submissions"
        ],
        "parameters": [
          {
            "name": "map",
            "in": "query",
            "required": false,
            "description": "When `true`, discover and submit multiple URLs from the site instead of just the one provided. Cannot be combined with a sections selection.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "max_urls",
            "in": "query",
            "required": false,
            "description": "Maximum number of URLs to discover in map mode. The server clamps this to 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmissionCreate"
              },
              "examples": {
                "full": {
                  "summary": "Full extraction",
                  "value": {
                    "url": "https://stripe.com"
                  }
                },
                "selective": {
                  "summary": "Extract colors and typography",
                  "value": {
                    "url": "https://linear.app",
                    "sections": [
                      "colors",
                      "typography"
                    ],
                    "force": true
                  }
                },
                "actionsNavigation": {
                  "summary": "Extract actions and navigation",
                  "value": {
                    "url": "https://stripe.com",
                    "sections": [
                      "actions",
                      "navigation"
                    ]
                  }
                },
                "brandAssets": {
                  "summary": "Extract fresh brand assets",
                  "value": {
                    "url": "https://cursor.com",
                    "sections": [
                      "brand_assets"
                    ],
                    "force": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The submission was accepted and queued for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionAccepted"
                },
                "example": {
                  "submission_id": "sub_2f9a1c7b",
                  "status": "accepted",
                  "source_url": "https://stripe.com",
                  "accepted_at": "2026-06-10T12:00:00Z",
                  "cache_hit": false
                }
              }
            }
          },
          "422": {
            "description": "Invalid request. Empty selections, unknown section names, or combining sections with enable_deep_analysis=true return request validation errors. Combining sections with map=true returns SELECTIVE_MAP_UNSUPPORTED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "SELECTIVE_MAP_UNSUPPORTED",
                    "message": "sections cannot be combined with map=true"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "operationId": "listSubmissions",
        "summary": "List submissions",
        "description": "Returns a paginated list of your submissions, newest first, deduplicated by URL. Filter by status, by the API key that created them, by batch, or by `kind` (website extractions, brand kits, or photography profiles), and search by URL with `q`.\n\nAlongside the page, `total` counts everything matching the filter and `totals` breaks that down into `completed`, `failed`, and `in_progress`.",
        "tags": [
          "Submissions"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter results to a single status.",
            "schema": {
              "$ref": "#/components/schemas/SubmissionStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free-text search over the source URL. Returns only submissions whose URL contains this substring (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "api_key_id",
            "in": "query",
            "required": false,
            "description": "Return only submissions created with this API key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "batch_id",
            "in": "query",
            "required": false,
            "description": "Return only submissions from this batch run. Photography profiles never belong to a batch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Return only website extractions, brand kits, or photography profiles.",
            "schema": {
              "$ref": "#/components/schemas/SubmissionKind"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of submissions to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of submissions to skip, for pagination.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/brand-kit": {
      "post": {
        "operationId": "createBrandKitSubmission",
        "summary": "Create brand kit submission",
        "description": "Extracts a brand kit from a brand guidelines PDF: profile and voice, colors with their proportions, typography, logo variants and misuse, photography, graphics, applications, layout, buttons, cards, motion, and the book's rules.\n\nThe job runs asynchronously: poll `GET /design/submissions/{id}/result` until the status is `completed` or `failed`. The result's `design_system` has `kind: \"brand_kit\"` and the [BrandKitDesignSystem](/concepts/brand-kit#read-the-result) shape.\n\nEvery upload is a fresh extraction and is never served from cache. The PDF can be up to 50 MB; the extractor reads up to the first 150 pages. See [Brand kit](/concepts/brand-kit).",
        "tags": [
          "Submissions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Brand guidelines PDF (brand book, brand toolkit, identity deck)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The brand kit was accepted and queued for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionAccepted"
                },
                "example": {
                  "submission_id": "6f1c2a9e-0b7d-4e55-9a3c-1d2e3f4a5b6c",
                  "status": "accepted",
                  "source_url": "file://brand-guidelines.pdf",
                  "accepted_at": "2026-10-16T12:00:00Z"
                }
              }
            }
          },
          "413": {
            "description": "The PDF is larger than 50 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "FILE_TOO_LARGE",
                    "message": "file exceeds 50000000 bytes"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The file is not a PDF (`INVALID_FILE_TYPE`) or is empty (`EMPTY_FILE`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "INVALID_FILE_TYPE",
                    "message": "file must be a PDF (.pdf)"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/photography": {
      "post": {
        "operationId": "createPhotographySubmission",
        "summary": "Create photography submission",
        "description": "Builds a photography profile from a brand's photos: the traits most of them share, what varies between them, and the tone measured from their pixels.\n\nSend 1 to 12 photos, each as an `https://` `url` or as `image_b64`. With one photo, the profile is that photo's own reading; with more, it keeps what most of them share. Graphics, renders, and composites are read and then left out of the profile.\n\nThe job runs asynchronously: poll [`GET /design/submissions/{id}`](/api-reference/endpoint/get-submission) until the status is `completed` or `failed`, then read the profile from [`GET /design/submissions/{id}/result`](/api-reference/endpoint/get-submission-result). A failed submission's `error` starts with `NOT_ENOUGH_PHOTOS` (none of the photos is a usable photograph: send others) or `PHOTO_READS_FAILED` (retry the same request).\n\nCosts 2 credits, refunded when no profile comes back. See [Photography profile](/concepts/photography-profile).",
        "tags": [
          "Submissions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhotographySubmissionCreate"
              },
              "examples": {
                "urls": {
                  "summary": "Photos by URL",
                  "value": {
                    "photos": [
                      {
                        "url": "https://images.example.com/campaign-1.jpg"
                      },
                      {
                        "url": "https://images.example.com/campaign-2.jpg"
                      }
                    ]
                  }
                },
                "upload": {
                  "summary": "An uploaded photo",
                  "value": {
                    "photos": [
                      {
                        "image_b64": "/9j/4AAQSkZJRgABAQ..."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The photos were accepted and queued for reading.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionAccepted"
                },
                "example": {
                  "submission_id": "5fb12de2-b640-4333-954a-a41703406b9a",
                  "status": "accepted",
                  "source_url": "https://images.example.com/campaign-1.jpg",
                  "accepted_at": "2026-10-27T12:34:43Z"
                }
              }
            }
          },
          "402": {
            "description": "Not enough credits (`INSUFFICIENT_CREDITS`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "INSUFFICIENT_CREDITS",
                    "message": "Not enough credits for this request.",
                    "balance": 0,
                    "required": 2
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation: no photos or more than 12, a photo with both or neither of `url` and `image_b64`, a URL that is not public `https://`, or an upload that is not a PNG, JPEG, or WebP image. `detail` lists the issues.",
            "content": {
              "application/json": {
                "example": {
                  "detail": [
                    {
                      "type": "value_error",
                      "loc": [
                        "body",
                        "photos",
                        0,
                        "url"
                      ],
                      "msg": "Value error, URL must be a valid https:// URL with a host",
                      "input": "http://images.example.com/campaign-1.jpg"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/{id}": {
      "get": {
        "operationId": "getSubmission",
        "summary": "Get submission",
        "description": "Returns the current status of a single submission, including which step it is on.",
        "tags": [
          "Submissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubmissionId"
          }
        ],
        "responses": {
          "200": {
            "description": "The submission status view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/{id}/result": {
      "get": {
        "operationId": "getSubmissionResult",
        "summary": "Get submission result",
        "description": "Returns the unified result envelope for a submission, including the extracted design system and stored artifacts.\n\nResults stream in: while the job runs you may receive a `200` with a partial `design_system`, and before the first checkpoint lands you receive `409 NOT_READY`. Keep polling until `status` is `completed` or `failed`.\n\nUse `sections` to narrow `result.design_system` to the sections you actually read. The full document is large, and `assets` and `layout` are commonly most of it.\n\nFor selective extractions, only originally requested sections are available. This GET filter does not schedule extraction work or add unrequested sections. The envelope sections field always records the original selection, or null for a full extraction.\n\nFor a photography submission (`kind: \"photography\"`), the result is the [PhotographyResult](/concepts/photography-profile#read-the-result) and is not streamed: it returns `409 NOT_READY` until the submission is `completed`. A failed photography submission also returns `409`, so poll [`GET /design/submissions/{id}`](/api-reference/endpoint/get-submission) and read its `error`. `sections` does not apply.",
        "tags": [
          "Submissions"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubmissionId"
          },
          {
            "name": "sections",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of design-system sections to include, e.g. ?sections=colors,typography. Omit for all originally requested sections. This only filters the existing result and never runs extraction work or adds sections omitted from the original submission. Only result.design_system is filtered: the envelope, including its original sections selection, and result.artifacts are always returned. An unknown section name returns 422. For a brand kit, use its own section names, such as ?sections=colors,buttons; a website section name returns 422.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/SectionName"
                  },
                  {
                    "$ref": "#/components/schemas/BrandKitSectionName"
                  }
                ]
              }
            },
            "example": "colors,typography"
          }
        ],
        "responses": {
          "200": {
            "description": "The result envelope. May be partial while later steps complete.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/SubmissionResult"
                    },
                    {
                      "$ref": "#/components/schemas/PhotographyResult"
                    }
                  ]
                },
                "examples": {
                  "selective": {
                    "summary": "Completed selective extraction",
                    "description": "Abbreviated illustrative response. Other envelope fields and extracted section details are omitted.",
                    "value": {
                      "submission_id": "sub_example",
                      "status": "completed",
                      "source_url": "https://linear.app",
                      "sections": [
                        "colors",
                        "typography"
                      ],
                      "cache_hit": false,
                      "result": {
                        "design_system": {
                          "colors": {
                            "insights": []
                          },
                          "typography": {
                            "insights": []
                          }
                        },
                        "artifacts": {}
                      }
                    }
                  },
                  "brandAssets": {
                    "summary": "Completed brand assets extraction",
                    "description": "Abbreviated illustrative response. The image URL is a placeholder.",
                    "value": {
                      "submission_id": "sub_example",
                      "status": "completed",
                      "source_url": "https://cursor.com",
                      "sections": [
                        "brand_assets"
                      ],
                      "cache_hit": false,
                      "result": {
                        "design_system": {
                          "brand_assets": {
                            "assets": [
                              {
                                "url": "https://storage.example.com/brand-assets/campaign.jpg",
                                "source": "x",
                                "content_type": "image/jpeg",
                                "category": "brand_identity",
                                "asset_description": "Black geometric logo centered on a white background."
                              }
                            ]
                          }
                        },
                        "artifacts": {}
                      }
                    }
                  },
                  "photography": {
                    "summary": "Completed photography profile",
                    "description": "Abbreviated response from six Ai2 photos. Most rules, variations, and outcomes are omitted.",
                    "value": {
                      "kind": "photography",
                      "id": "5fb12de2-b640-4333-954a-a41703406b9a",
                      "status": "completed",
                      "photos": 6,
                      "profile": {
                        "id": "5fb12de2-b640-4333-954a-a41703406b9a",
                        "schema_version": "sheet-v2-draft-1",
                        "photos": 4,
                        "rules": [
                          {
                            "field": "focal_structure",
                            "name": "Focal structure",
                            "value": "single_dominant",
                            "share": 0.75
                          },
                          {
                            "field": "vignette",
                            "name": "Vignette",
                            "value": "none",
                            "share": 1.0
                          }
                        ],
                        "varies": [
                          {
                            "field": "shot_scale",
                            "name": "Shot scale and crop margin",
                            "values": {
                              "close": 2,
                              "detail": 2
                            }
                          }
                        ],
                        "tone": [
                          {
                            "field": "exposure_key",
                            "name": "Exposure key",
                            "value": "mid_key",
                            "measure": 47.29
                          }
                        ],
                        "signature": {
                          "darkest_lightness": 1.6,
                          "brightest_lightness": 95.0,
                          "clipped_highlights_percent": 0.061,
                          "midtone_warmth": 22.9,
                          "shadow_warmth": 0.6,
                          "colourfulness": 72.0,
                          "lightness_spread": 24.9
                        },
                        "palette": [
                          "#1f1914",
                          "#504745",
                          "#e3e2e1",
                          "#dc9912",
                          "#e9b73f"
                        ]
                      },
                      "outcomes": [
                        {
                          "index": 1,
                          "status": "read",
                          "main_subject": "A single yellow sunflower head with a dark central disc, seen face-on",
                          "excluded": null,
                          "error": null
                        },
                        {
                          "index": 3,
                          "status": "read",
                          "main_subject": "Undulating glossy wave surface filling the frame; no discrete object",
                          "excluded": "photoreal_render",
                          "error": null
                        }
                      ],
                      "error": null,
                      "created_at": "2026-10-27T12:34:42Z",
                      "completed_at": "2026-10-27T12:35:27Z"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The result is not ready yet. Keep polling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "NOT_READY",
                    "message": "Result is not ready yet."
                  }
                }
              }
            }
          },
          "422": {
            "description": "One of the requested section names is not a valid section.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "INVALID_REQUEST",
                    "message": "Unknown section name."
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/{id}/download": {
      "get": {
        "operationId": "downloadSubmission",
        "summary": "Download submission bundle",
        "description": "Returns a manifest of every file that makes up a completed extraction, so you can save the whole thing to disk: `guidelines.json` (the design system, inline in `content`), the captured HTML, CSS, and screenshot, and the logos, media, and icons referenced by the design system.\n\nEach entry names a relative `path` and carries either inline `content` or a `url` to download. Assets are mirrored to the engine's own storage first, so the URLs stay valid even when the origin site changes.\n\nAuthentication is optional, on the same terms as the result endpoint. The submission must be `completed`: an in-progress one returns `409 NOT_READY`.\n\nFor selective extractions, guidelines.json contains only the originally requested design-system sections.\n\nWhen brand_assets is present in the result, the manifest includes its stored images under brand-assets/.\n\nPhotography submissions have no bundle: read the profile from [`GET /design/submissions/{id}/result`](/api-reference/endpoint/get-submission-result).",
        "tags": [
          "Submissions"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubmissionId"
          }
        ],
        "responses": {
          "200": {
            "description": "The bundle manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionManifest"
                },
                "example": {
                  "submission_id": "sub_2f9a1c7b",
                  "extraction_id": "ext_8d3e5a10",
                  "files": [
                    {
                      "path": "guidelines.json",
                      "content": "{\n  \"profile\": { ... }\n}"
                    },
                    {
                      "path": "crawl/html.html",
                      "url": "https://storage.example.com/..."
                    },
                    {
                      "path": "crawl/screenshot.jpeg",
                      "url": "https://storage.example.com/..."
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The extraction has not completed yet. Keep polling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "NOT_READY",
                    "message": "Submission is extracting"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/submissions/{id}/visibility": {
      "patch": {
        "operationId": "updateSubmissionVisibility",
        "summary": "Update visibility",
        "description": "Toggles whether a submission's result is public. Public results can be read from `GET /submissions/{id}/result` without authentication. Only the owner of the submission can change its visibility.",
        "tags": [
          "Submissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubmissionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VisibilityUpdate"
              },
              "example": {
                "is_public": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated submission status view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/design/prompts/enhance": {
      "post": {
        "operationId": "enhancePrompt",
        "summary": "Enhance prompt",
        "description": "Rewrites a prompt so that it is grounded in the brand profile extracted for a given submission. Useful for steering downstream generation toward a specific brand's look and feel.\n\nRequires a completed full extraction, created without sections. Selective submissions return 422 FULL_EXTRACTION_REQUIRED, even if they requested profile or were served from a full cached extraction.",
        "tags": [
          "Prompts"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnhanceRequest"
              },
              "example": {
                "submission_id": "sub_2f9a1c7b",
                "prompt": "Design a pricing page"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enhanced prompt and the brand context used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnhanceResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request payload, or a selective submission that cannot be used for prompt enhancement (FULL_EXTRACTION_REQUIRED). Submit the URL without sections for a full extraction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "FULL_EXTRACTION_REQUIRED",
                    "message": "This operation requires a full extraction. Submit the URL without sections."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/search": {
      "post": {
        "operationId": "searchBrands",
        "summary": "Search brands",
        "description": "Searches the curated brand corpus for the aesthetic you describe in natural language, and returns ranked result cards.\n\nRuns synchronously: there is nothing to poll. Consumes credits (more at `deep` depth), refunded if the search fails.\n\nResults come from the corpus, not from your own submissions. To work with a result, extract it with [`POST /design/submissions`](/api-reference/endpoint/create-submission).\n\nAn optional `filters` object narrows results as hard constraints (`page_type`, `industry`, `hue`, `layout`): every returned card satisfies them.",
        "tags": [
          "Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandSearchRequest"
              },
              "example": {
                "query": "dark brutalist developer tools",
                "depth": "fast",
                "top_k": 6
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked results for the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandSearchResults"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/SearchFailed"
          },
          "503": {
            "$ref": "#/components/responses/SearchSaturated"
          }
        }
      }
    },
    "/search/similar": {
      "get": {
        "operationId": "findSimilarBrands",
        "summary": "Find similar brands",
        "description": "Returns brands from the corpus that look like one of your completed extractions, as the same result cards as [`POST /search`](/api-reference/endpoint/search-brands).\n\nRuns synchronously. Consumes credits, refunded if the request fails. The submission must be completed and readable by the caller.\n\nNeighbours are computed fresh on every call, so they can vary slightly between calls for the same submission.\n\nRequires a full extraction, created without sections. Selective submissions return 422 FULL_EXTRACTION_REQUIRED, including those served from a full cached extraction.",
        "tags": [
          "Search"
        ],
        "parameters": [
          {
            "name": "submission_id",
            "in": "query",
            "required": true,
            "description": "A completed full extraction of yours whose visual neighbours you want. Selective submissions are not supported.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "top_k",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 30,
              "default": 18
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Brands visually similar to the submission.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimilarBrandsResults"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "description": "No completed extraction for that submission.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "NOT_FOUND",
                    "message": "No completed extraction for that submission."
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/SearchFailed"
          },
          "422": {
            "$ref": "#/components/responses/FullExtractionRequired"
          }
        }
      }
    },
    "/search/submissions": {
      "get": {
        "operationId": "listSearches",
        "summary": "List searches",
        "description": "Returns your past searches and similarity lookups, newest first. Free: listing history costs no credits.\n\nSearch results themselves are not stored, only the request and its outcome. Extraction history lives at [`GET /design/submissions`](/api-reference/endpoint/list-submissions).",
        "tags": [
          "Search"
        ],
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter to query searches or similarity lookups.",
            "schema": {
              "type": "string",
              "enum": [
                "search",
                "similar"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by outcome.",
            "schema": {
              "type": "string",
              "enum": [
                "completed",
                "failed",
                "in_progress"
              ]
            }
          },
          {
            "name": "depth",
            "in": "query",
            "required": false,
            "description": "Filter query searches by depth.",
            "schema": {
              "type": "string",
              "enum": [
                "fast",
                "deep"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free-text filter over the query text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "api_key_id",
            "in": "query",
            "required": false,
            "description": "Only searches made with one API key. Useful for separating environments that share an account.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of searches to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of searches to skip, for pagination.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of past searches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchSubmissionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/judge/brand-adherence": {
      "post": {
        "operationId": "createBrandAdherence",
        "summary": "Create brand-adherence job",
        "description": "Judges how well a page (the `source_url`) adheres to a reference site's brand (the `reference_url`). Use it to score a rebuild, a generated page, or a redesign against the brand it should follow.\n\nThe job runs asynchronously: poll `GET /judge/brand-adherence/{job_id}` until the status is `completed` or `failed`, then read the verdict from `GET /judge/brand-adherence/{job_id}/result`.\n\nBoth URLs are extracted automatically, and a recent extraction of either side is reused. You do not need to create submissions first.",
        "tags": [
          "Brand adherence"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdherenceCreate"
              },
              "example": {
                "reference_url": "https://stripe.com",
                "source_url": "https://staging.example.com"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The job was accepted and queued for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdherenceAccepted"
                },
                "example": {
                  "job_id": "0f5b2c9a-6a1e-4b0e-9f0e-1c2d3e4f5a6b",
                  "status": "accepted",
                  "reference_url": "https://stripe.com",
                  "source_url": "https://staging.example.com",
                  "accepted_at": "2026-08-25T12:00:00Z",
                  "is_public": false
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "404": {
            "description": "Reference or source submission not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "SUBMISSION_NOT_FOUND",
                    "message": "Reference or source submission not found"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listBrandAdherenceJobs",
        "summary": "List brand-adherence jobs",
        "description": "Lists the caller's brand-adherence jobs, most recent first. Filter by `status`; paginate with `limit` and `offset`.",
        "tags": [
          "Brand adherence"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by lifecycle status. A job is stored as `accepted`, `completed`, or `failed`; `extracting` and `judging` are live states seen only when polling a single job, so filtering by them returns an empty list.",
            "schema": {
              "$ref": "#/components/schemas/AdherenceStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free-text search over both sides' URLs.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum jobs to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of jobs to skip, for pagination.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's jobs, most recent first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdherenceJobList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/judge/brand-adherence/{job_id}": {
      "get": {
        "operationId": "getBrandAdherenceJob",
        "summary": "Get brand-adherence job",
        "description": "Returns the live status of a brand-adherence job. `status` advances through `accepted`, `extracting`, and `judging` before reaching `completed` or `failed`; `current_step` names the pipeline stage in progress.\n\nPoll this endpoint, then read the verdict from `GET /judge/brand-adherence/{job_id}/result` once `status` is `completed`. On `failed`, `error` carries the reason.",
        "tags": [
          "Brand adherence"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AdherenceJobId"
          }
        ],
        "responses": {
          "200": {
            "description": "The job's status view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdherenceJob"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/judge/brand-adherence/{job_id}/visibility": {
      "patch": {
        "operationId": "updateBrandAdherenceVisibility",
        "summary": "Update job visibility",
        "description": "Toggles whether a job's verdict is public. Public verdicts can be read from `GET /judge/brand-adherence/{job_id}/result` without authentication. Only the owner of the job can change its visibility.",
        "tags": [
          "Brand adherence"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AdherenceJobId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VisibilityUpdate"
              },
              "example": {
                "is_public": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated job status view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdherenceJob"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/judge/brand-adherence/{job_id}/result": {
      "get": {
        "operationId": "getBrandAdherenceVerdict",
        "summary": "Get brand-adherence verdict",
        "description": "Returns the verdict for a brand-adherence job: one `score`, the `recommendations`, and the `fixes`.\n\nThe verdict streams in: while the job runs you may receive a `200` with a partial verdict (components the run has not produced yet stay `null` or empty), and before the first component lands you receive `409 NOT_READY`. Keep polling until `status` is `completed`. A failed job returns `424` and will never produce a verdict, so stop polling on it.\n\nAuthentication is optional here: with a valid key you can read your own jobs; without one, only jobs made public are returned.",
        "tags": [
          "Brand adherence"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AdherenceJobId"
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict. May be partial while the job runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdherenceVerdict"
                },
                "example": {
                  "job_id": "0f5b2c9a-6a1e-4b0e-9f0e-1c2d3e4f5a6b",
                  "status": "completed",
                  "reference_url": "https://stripe.com",
                  "source_url": "https://staging.example.com",
                  "is_public": false,
                  "accepted_at": "2026-08-25T12:00:00Z",
                  "completed_at": "2026-08-25T12:01:10Z",
                  "score": 0.85,
                  "recommendations": [
                    "Snap the hero heading from 44px to 48px to match the reference type scale."
                  ],
                  "fixes": [
                    {
                      "action": "snap_to_token",
                      "property": "font_size",
                      "from": "44px",
                      "to_value": "48px"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The verdict is not ready yet. Keep polling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "NOT_READY",
                    "message": "Brand adherence job is extracting"
                  }
                }
              }
            }
          },
          "424": {
            "description": "The job failed permanently. No verdict will be produced, so stop polling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "error": "BRAND_ADHERENCE_FAILED",
                    "message": "Brand adherence job failed"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key issued by the Taste Engine. Create one in the dashboard at https://engine.tastelabs.com/app/api-keys."
      }
    },
    "parameters": {
      "SubmissionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The `submission_id` returned when the submission was created.",
        "schema": {
          "type": "string"
        }
      },
      "AdherenceJobId": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "description": "Brand-adherence job identifier, as returned by `POST /judge/brand-adherence`.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The API key is missing, invalid, or expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": {
                "error": "UNAUTHORIZED",
                "message": "Bearer token or X-API-Key required"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key is valid but is not allowed to call this endpoint.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": {
                "error": "FORBIDDEN",
                "message": "This key is not allowed to call this endpoint."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource was not found or is not accessible with these credentials.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": {
                "error": "NOT_FOUND",
                "message": "Submission not found"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "An unexpected server error occurred. The body is plain text.",
        "content": {
          "text/plain": {
            "example": "Internal Server Error"
          }
        }
      },
      "InsufficientCredits": {
        "description": "The account doesn't have enough credits to run the request. Only occurs when credit metering is enabled.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "detail"
              ],
              "properties": {
                "detail": {
                  "type": "object",
                  "required": [
                    "error",
                    "message"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "examples": [
                        "INSUFFICIENT_CREDITS"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "balance": {
                      "type": "integer",
                      "description": "The account's current credit balance."
                    },
                    "required": {
                      "type": "integer",
                      "description": "Credits required to run this request."
                    }
                  }
                }
              }
            },
            "example": {
              "detail": {
                "error": "INSUFFICIENT_CREDITS",
                "message": "Not enough credits for this request.",
                "balance": 0,
                "required": 1
              }
            }
          }
        }
      },
      "SearchFailed": {
        "description": "The search could not be completed. The credit charge is refunded. `detail` is a plain string.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "detail": {
                  "type": "string"
                }
              }
            },
            "example": {
              "detail": "Search failed."
            }
          }
        }
      },
      "SearchSaturated": {
        "description": "Search is at capacity. Retry after the number of seconds in the `Retry-After` header. The credit charge is refunded. `detail` is a plain string.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "detail": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ValidationError": {
        "description": "The request failed validation. `detail` is a list of issues, each naming the offending field in `loc`.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "detail": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "loc": {
                        "type": "array",
                        "items": {}
                      },
                      "msg": {
                        "type": "string"
                      },
                      "type": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            },
            "example": {
              "detail": [
                {
                  "loc": [
                    "body",
                    "url"
                  ],
                  "msg": "Field required",
                  "type": "missing"
                }
              ]
            }
          }
        }
      },
      "FullExtractionRequired": {
        "description": "The operation requires a completed full extraction. Selective submissions are rejected, including those served from a full cache hit. Submit the URL without sections.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": {
                "error": "FULL_EXTRACTION_REQUIRED",
                "message": "This operation requires a full extraction. Submit the URL without sections."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Standard error envelope for non-2xx responses: the machine-readable `error` code and the human-readable `message` are nested under `detail`. Validation errors (`422`) and search failures (`502`, `503`) shape `detail` differently; see those responses.",
        "properties": {
          "detail": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "Machine-readable error code.",
                "examples": [
                  "UNAUTHORIZED",
                  "FORBIDDEN",
                  "NOT_FOUND",
                  "NOT_READY"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation of the error."
              }
            },
            "required": [
              "error",
              "message"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "detail"
        ]
      },
      "Me": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` when authenticated with an API key."
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` when authenticated with an API key."
          }
        },
        "required": [
          "user_id"
        ]
      },
      "SubmissionStatus": {
        "type": "string",
        "description": "Lifecycle status of a submission.",
        "enum": [
          "accepted",
          "queued",
          "crawling",
          "extracting",
          "completed",
          "failed"
        ]
      },
      "SubmissionCreate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to extract a design system from."
          },
          "force": {
            "type": "boolean",
            "default": false,
            "description": "Re-run extraction even if a cached result exists."
          },
          "enable_deep_analysis": {
            "type": "boolean",
            "default": false,
            "description": "Run additional, slower analysis steps. Cannot be combined with sections."
          },
          "sections": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/SectionName"
            },
            "minItems": 1,
            "default": null,
            "description": "Design-system sections to extract and return. Omit or pass null for a full extraction. Empty arrays and unknown names return 422; duplicates are removed. Cannot be combined with map=true or enable_deep_analysis=true. An explicit list of every section is still a selective extraction. Selecting fewer sections does not change the credit price. Use [\"brand_assets\"] to extract only brand imagery from ads, social channels, and campaigns.",
            "example": [
              "colors",
              "typography"
            ]
          }
        },
        "required": [
          "url"
        ]
      },
      "SubmissionAccepted": {
        "type": "object",
        "properties": {
          "submission_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted"
            ]
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "submission_id",
          "status",
          "source_url",
          "accepted_at"
        ]
      },
      "Submission": {
        "type": "object",
        "description": "Status view of a single submission.",
        "properties": {
          "submission_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "current_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the step currently running. `null` before the first step and for brand kit and photography submissions."
          },
          "step_started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cache_hit": {
            "type": "boolean",
            "description": "Whether the result was served from cache. When `true`, `completed_at` reflects the original extraction, not this request."
          },
          "extraction_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the extraction completed: the freshness of the result. On a cache hit (`cache_hit` is `true`) this is the original extraction, not the current request, so it may predate this request. `null` while the submission is still running."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Error message when status is `failed`."
          },
          "credits_consumed": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Net credits this submission consumed. `null` when metering was off, and `0` once a failed submission has been refunded."
          },
          "credit_ref_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reference to the credit charge for this submission."
          },
          "api_key_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the API key that created the submission. `null` for dashboard submissions and when metering was off at submission time."
          },
          "batch_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the batch run this submission belongs to."
          },
          "batch_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable label for the batch run."
          },
          "is_public": {
            "type": "boolean",
            "description": "Whether the result is publicly readable."
          },
          "sections": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/SectionName"
            },
            "default": null,
            "description": "The original extraction selection, with duplicates removed. Null identifies a full extraction. This value is unchanged by GET result filtering. Completed selective results contain only the requested sections and are never eligible cache sources.",
            "example": [
              "colors",
              "typography"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/SubmissionKind"
          }
        },
        "required": [
          "submission_id",
          "status",
          "source_url",
          "is_public"
        ]
      },
      "SubmissionList": {
        "type": "object",
        "properties": {
          "submissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Submission"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total submissions matching the filter."
          },
          "totals": {
            "$ref": "#/components/schemas/SubmissionTotals"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "submissions",
          "total",
          "limit",
          "offset"
        ]
      },
      "VisibilityUpdate": {
        "type": "object",
        "properties": {
          "is_public": {
            "type": "boolean",
            "description": "Set to `true` to make the result publicly readable, `false` to make it private."
          }
        },
        "required": [
          "is_public"
        ]
      },
      "Artifact": {
        "type": "object",
        "description": "A stored file produced during extraction.",
        "properties": {
          "path": {
            "type": "string",
            "description": "Storage path of the artifact."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to download the artifact."
          },
          "content_type": {
            "type": "string",
            "description": "MIME type, e.g. `text/html`."
          }
        }
      },
      "BrandAsset": {
        "type": "object",
        "description": "An accepted brand image stored in Supabase. Source-page links, judge scores, and rejected images are not returned.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the stored image. Use it to display or download the image; it is not the original post or ad URL."
          },
          "source": {
            "type": "string",
            "description": "Where the image was found. See [Brand asset sources](/concepts/brand-assets#sources).",
            "enum": [
              "google_ads",
              "meta_ads",
              "email",
              "instagram_highlights",
              "x",
              "linkedin",
              "billboard"
            ]
          },
          "content_type": {
            "type": "string",
            "description": "Image MIME type, such as image/jpeg or image/png."
          },
          "category": {
            "type": "string",
            "description": "The aspect of the brand identity this image represents. See [Categories](/concepts/brand-assets#categories).",
            "enum": [
              "brand_identity",
              "brand_photography_identity",
              "partnership_collaboration_identity"
            ]
          },
          "asset_description": {
            "type": "string",
            "description": "A short, one-sentence description of the visible subject and design."
          }
        },
        "required": [
          "url",
          "source",
          "content_type",
          "category",
          "asset_description"
        ],
        "example": {
          "url": "https://storage.example.com/brand-assets/campaign.jpg",
          "source": "x",
          "content_type": "image/jpeg",
          "category": "brand_identity",
          "asset_description": "Black geometric logo centered on a white background."
        }
      },
      "BrandAssetsSection": {
        "type": [
          "object",
          "null"
        ],
        "description": "Brand imagery from ads, social channels, and campaigns. Included by default in fresh full extractions, or selected with sections=[\"brand_assets\"]. An empty assets array is a successful result with no accepted images. Null can mean pending work, an older full result, or a processing failure. Omitted when not selected or excluded by a GET filter. See [Brand assets](/concepts/brand-assets).",
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandAsset"
            },
            "default": [],
            "description": "Accepted images. Rejected candidates are not stored or returned."
          }
        },
        "required": [
          "assets"
        ],
        "default": null
      },
      "SubmissionResult": {
        "type": "object",
        "description": "Unified result envelope. May be partial while later steps complete.",
        "properties": {
          "submission_id": {
            "type": "string"
          },
          "extraction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the extraction this submission produced. `null` until one exists."
          },
          "status": {
            "type": "string",
            "description": "Submission status, e.g. `completed` or `failed`."
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "current_step": {
            "type": "string"
          },
          "step_started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "result_updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "cache_hit": {
            "type": "boolean",
            "description": "Whether the result was served from cache. When `true`, `completed_at` reflects the original extraction, not this request."
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the extraction completed: the freshness of the result. On a cache hit (`cache_hit` is `true`) this is the original extraction, not the current request, so it may predate this request. `null` while the submission is still running."
          },
          "result": {
            "type": "object",
            "properties": {
              "design_system": {
                "type": "object",
                "additionalProperties": true,
                "description": "The extracted design system: a `profile` object (including `profile.brand_name` and nullable `profile.tagline`), color palettes, typography, spacing, component styles, and brand_assets for imagery from ads, social channels, and campaigns. See [The design system](/concepts/design-system) for the structure. For selective submissions, only originally requested sections appear. The sections query filter can narrow this further but cannot add unrequested sections. For a brand kit (`kind` is `brand_kit`), the shape is [BrandKitDesignSystem](/concepts/brand-kit#read-the-result).",
                "properties": {
                  "profile": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Brand identity and intent. See [The brand profile](/concepts/design-system#the-brand-profile) for all fields.",
                    "properties": {
                      "tagline": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "default": null,
                        "description": "Existing brand tagline copied from the submitted page, preserving its language, capitalization, and punctuation. Null when absent, unreadable, or ambiguous. The extractor does not generate a slogan or substitute a hero headline. Older cached extractions may return null; submit with force: true for a fresh extraction.",
                        "examples": [
                          "Gives You Wings®",
                          null
                        ]
                      }
                    }
                  },
                  "brand_assets": {
                    "$ref": "#/components/schemas/BrandAssetsSection"
                  }
                }
              },
              "artifacts": {
                "type": "object",
                "properties": {
                  "html": {
                    "$ref": "#/components/schemas/Artifact"
                  },
                  "css": {
                    "$ref": "#/components/schemas/Artifact"
                  },
                  "screenshot": {
                    "$ref": "#/components/schemas/Artifact",
                    "description": "Screenshot captured during the crawl."
                  },
                  "full_page_screenshot": {
                    "$ref": "#/components/schemas/Artifact",
                    "description": "Full-page screenshot of the rendered page."
                  },
                  "element_layout": {
                    "$ref": "#/components/schemas/Artifact",
                    "description": "Measured element geometry captured during the crawl."
                  }
                }
              }
            }
          },
          "sections": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/SectionName"
            },
            "default": null,
            "description": "The original extraction selection, with duplicates removed. Null identifies a full extraction. This value is unchanged by GET result filtering. Completed selective results contain only the requested sections and are never eligible cache sources.",
            "example": [
              "colors",
              "typography"
            ]
          }
        },
        "required": [
          "submission_id",
          "status",
          "source_url"
        ]
      },
      "EnhanceRequest": {
        "type": "object",
        "properties": {
          "submission_id": {
            "type": "string",
            "description": "Completed full extraction whose brand profile grounds the rewrite. Selective submissions are not supported."
          },
          "prompt": {
            "type": "string",
            "description": "The prompt to enhance."
          }
        },
        "required": [
          "submission_id",
          "prompt"
        ]
      },
      "EnhanceResponse": {
        "type": "object",
        "properties": {
          "enhanced_prompt": {
            "type": "string"
          },
          "brand_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Brand name from the grounding submission, when one was identified."
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "model_used": {
            "type": "string"
          }
        },
        "required": [
          "enhanced_prompt"
        ]
      },
      "SearchDepth": {
        "type": "string",
        "description": "Search depth. `fast` returns in seconds. `deep` consumes more credits and can take up to ~3 minutes, for when result quality matters more than latency.",
        "enum": [
          "fast",
          "deep"
        ],
        "default": "fast"
      },
      "BrandSearchRequest": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "Natural-language description of the aesthetic you are looking for.",
            "minLength": 2,
            "maxLength": 400,
            "examples": [
              "dark brutalist developer tools"
            ]
          },
          "depth": {
            "$ref": "#/components/schemas/SearchDepth"
          },
          "top_k": {
            "type": "integer",
            "description": "Maximum number of results to return.",
            "minimum": 1,
            "maximum": 30,
            "default": 6
          },
          "filters": {
            "type": [
              "object",
              "null"
            ],
            "description": "Hard constraints on the results, not a ranking hint: every returned card satisfies them, and the list is never padded with out-of-filter lookalikes. Values are matched verbatim against the corpus vocabulary, so an unknown value returns no matches rather than an error.",
            "properties": {
              "page_type": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only brands with this page type.",
                "enum": [
                  "homepage",
                  "about",
                  "pricing",
                  "careers",
                  "case_study",
                  "features",
                  "customer_stories",
                  "portfolio",
                  "product",
                  "collections",
                  "solutions",
                  "blog",
                  "landing_page",
                  "team",
                  "services",
                  null
                ]
              },
              "industry": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only brands in this industry.",
                "enum": [
                  "saas",
                  "ai",
                  "design",
                  "creative_agency",
                  "fintech",
                  "branding",
                  "developer_tools",
                  "fashion",
                  "venture_capital",
                  "entertainment",
                  "beverage",
                  "food",
                  "cybersecurity",
                  "e_commerce",
                  "architecture",
                  "cloud_infrastructure",
                  "ai_infrastructure",
                  "healthcare",
                  "edtech",
                  "web_development",
                  "productivity",
                  "beauty",
                  "photography",
                  "hr_tech",
                  "media",
                  "hospitality",
                  "gaming",
                  "financial_services",
                  "wellness",
                  "investment",
                  "real_estate",
                  "proptech",
                  "web3",
                  "restaurant",
                  "insurtech",
                  "insurance",
                  "nonprofit",
                  null
                ]
              },
              "hue": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only brands whose dominant palette hues include this hue.",
                "enum": [
                  "red",
                  "orange",
                  "yellow",
                  "green",
                  "teal",
                  "blue",
                  "purple",
                  "pink",
                  "brown",
                  null
                ]
              },
              "layout": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only brands with this layout classification.",
                "enum": [
                  "grid_based_strict",
                  "asymmetric_broken_grid",
                  "generous_whitespace",
                  "dense_packed",
                  "centered_symmetric",
                  "left_aligned_flush",
                  "card_based",
                  "overlapping_layered",
                  null
                ]
              }
            }
          }
        },
        "required": [
          "query"
        ]
      },
      "BrandSearchCard": {
        "type": "object",
        "description": "One ranked brand from the corpus. Ranking scores stay internal and are not part of the response.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL of the brand's page in the corpus."
          },
          "brand_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Brand name, when one was identified."
          },
          "identity_paragraph": {
            "type": "string",
            "description": "A short description of the brand's visual identity."
          },
          "match": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "strong",
              "good",
              "related",
              null
            ],
            "description": "Confidence tier of the match. `null` when the search did not assign one, as on similarity results."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "One sentence on why this brand matched the query. `null` when the search did not assign one."
          },
          "badge": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "discovery",
              null
            ],
            "description": "`discovery` marks a rotating exemplar of the style detected in the query, included so identical searches do not return an identical set. `null` otherwise."
          },
          "tags": {
            "type": "array",
            "description": "Descriptive tags for the brand, e.g. industry and page type.",
            "items": {
              "type": "string"
            }
          },
          "palette": {
            "type": [
              "object",
              "null"
            ],
            "description": "Palette classification: `luminance`, `temperature`, `saturation`, `themes`, `aesthetic_codes`, `effects`, plus `primary` and `secondary` hex lists."
          },
          "typography": {
            "type": [
              "object",
              "null"
            ],
            "description": "Typography classification: `family_strategy`, `hierarchy`, and `character`."
          },
          "screenshot_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Full-page screenshot of the brand's page. Fetch it to see the brand."
          }
        },
        "required": [
          "url",
          "identity_paragraph"
        ]
      },
      "BrandSearchResults": {
        "type": "object",
        "properties": {
          "search_id": {
            "type": "string",
            "description": "Identifier of this search, as recorded in your search history."
          },
          "depth": {
            "$ref": "#/components/schemas/SearchDepth"
          },
          "results": {
            "type": "array",
            "description": "Ranked results, best match first.",
            "items": {
              "$ref": "#/components/schemas/BrandSearchCard"
            }
          },
          "query_tags": {
            "type": [
              "object",
              "null"
            ],
            "description": "The engine's soft reading of the query, for display only. Never a constraint: results are not guaranteed to satisfy these. Only the request's `filters` narrow retrieval.",
            "properties": {
              "industry": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "page_type": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "hue": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "aesthetic": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "style": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Display name of the detected design style, e.g. `Brutalism`. Only set when the detection is confident."
              }
            }
          }
        }
      },
      "SimilarBrandsResults": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "description": "Nearest visual neighbours, closest first.",
            "items": {
              "$ref": "#/components/schemas/BrandSearchCard"
            }
          }
        }
      },
      "SearchSubmission": {
        "type": "object",
        "description": "One past search. Results are not stored, only the request and its outcome.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier of the search."
          },
          "kind": {
            "type": "string",
            "description": "`search` for a query search, `similar` for a similarity lookup.",
            "enum": [
              "search",
              "similar"
            ]
          },
          "query": {
            "type": "string",
            "description": "The query text, or the source URL for a `similar` lookup."
          },
          "depth": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SearchDepth"
              },
              {
                "type": "null"
              }
            ],
            "description": "Depth used. `null` for `similar` lookups."
          },
          "filters": {
            "type": [
              "object",
              "null"
            ],
            "description": "The hard constraints sent with the search, when any."
          },
          "query_tags": {
            "type": [
              "object",
              "null"
            ],
            "description": "The engine's soft reading of the query, as returned with the results."
          },
          "status": {
            "type": "string",
            "description": "Outcome of the search."
          },
          "result_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many results the search returned."
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How long the search took, in milliseconds."
          },
          "credit_ref_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reference to the credit charge for this search."
          },
          "credits_consumed": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Credits consumed by this search, net of any refund."
          },
          "api_key_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the API key that made the search, when one was used."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "SearchSubmissionList": {
        "type": "object",
        "properties": {
          "submissions": {
            "type": "array",
            "description": "Past searches, newest first.",
            "items": {
              "$ref": "#/components/schemas/SearchSubmission"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total searches matching the filter, ignoring pagination."
          },
          "total_by_status": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Counts by outcome under the other filters, ignoring pagination, so a status picker can label its options."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "SectionName": {
        "type": "string",
        "description": "A top-level section of `result.design_system`. Use in the submission body to select extraction work, or in the result query to filter an existing result. The value \"sections\" means descriptions of the page sections in order.",
        "enum": [
          "profile",
          "layout",
          "colors",
          "typography",
          "surfaces",
          "elevation",
          "interactions",
          "actions",
          "navigation",
          "data_display",
          "structure",
          "icons",
          "assets",
          "sections",
          "brand_assets"
        ]
      },
      "SubmissionTotals": {
        "type": "object",
        "description": "Counts by outcome across everything matching the filter, ignoring pagination. The four in-progress statuses fold into `in_progress`, so the three buckets sum to `total`.",
        "properties": {
          "completed": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "in_progress": {
            "type": "integer"
          }
        }
      },
      "SubmissionManifestEntry": {
        "type": "object",
        "description": "One file in the bundle. Exactly one of `content` or `url` is set: `content` is inline text to write at `path`, `url` is a file to download to `path`.",
        "properties": {
          "path": {
            "type": "string",
            "description": "Relative path to write the file to, e.g. `crawl/html.html`."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Download URL, for files stored as artifacts."
          },
          "content": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inline file contents, for files generated from the result."
          }
        },
        "required": [
          "path"
        ]
      },
      "SubmissionManifest": {
        "type": "object",
        "description": "A flat list of the files that make up a completed extraction.",
        "properties": {
          "submission_id": {
            "type": "string"
          },
          "extraction_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubmissionManifestEntry"
            }
          }
        },
        "required": [
          "submission_id",
          "files"
        ]
      },
      "AdherenceStatus": {
        "type": "string",
        "description": "Lifecycle status of a brand-adherence job.",
        "enum": [
          "accepted",
          "extracting",
          "judging",
          "completed",
          "failed"
        ]
      },
      "AdherenceCreate": {
        "type": "object",
        "description": "A pair of URLs: the reference brand and the page to judge against it.",
        "properties": {
          "reference_url": {
            "type": "string",
            "format": "uri",
            "description": "URL of the reference site whose brand is the standard to meet."
          },
          "candidate_url": {
            "type": "string",
            "format": "uri",
            "description": "URL of the page to judge against the reference brand, e.g. a rebuild, a generated page, or a redesign."
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "deprecated": true,
            "description": "Deprecated alias for candidate_url, accepted on input for backward compatibility. Send one of the two, not both."
          }
        },
        "required": [
          "reference_url",
          "candidate_url"
        ]
      },
      "AdherenceSideReuse": {
        "type": "object",
        "description": "Which sides of the pair were served from an existing artifact.",
        "properties": {
          "reference": {
            "type": "boolean"
          },
          "source": {
            "type": "boolean",
            "description": "The candidate side. The key keeps its historical name."
          }
        }
      },
      "AdherenceCacheHit": {
        "type": "object",
        "description": "Per-side reuse at each pipeline layer. Reuse makes the job faster.",
        "properties": {
          "design_extraction": {
            "$ref": "#/components/schemas/AdherenceSideReuse"
          },
          "verifier_extraction": {
            "$ref": "#/components/schemas/AdherenceSideReuse"
          },
          "final_report": {
            "type": "boolean",
            "description": "Whether the final pair-specific report was reused."
          }
        }
      },
      "AdherenceAccepted": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/AdherenceStatus"
          },
          "reference_url": {
            "type": "string",
            "format": "uri"
          },
          "candidate_url": {
            "type": "string",
            "format": "uri"
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "deprecated": true,
            "description": "Deprecated mirror of candidate_url."
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "is_public": {
            "type": "boolean"
          },
          "cache_hit": {
            "$ref": "#/components/schemas/AdherenceCacheHit"
          }
        },
        "required": [
          "job_id",
          "status",
          "reference_url",
          "candidate_url",
          "source_url",
          "accepted_at"
        ]
      },
      "AdherenceJob": {
        "type": "object",
        "description": "Status view of a single brand-adherence job.",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/AdherenceStatus"
          },
          "reference_url": {
            "type": "string",
            "format": "uri"
          },
          "candidate_url": {
            "type": "string",
            "format": "uri"
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "deprecated": true,
            "description": "Deprecated mirror of candidate_url."
          },
          "current_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the pipeline stage currently running, e.g. `waiting_for_extractions`, `judging_scene`."
          },
          "step_started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cache_hit": {
            "$ref": "#/components/schemas/AdherenceCacheHit"
          },
          "is_public": {
            "type": "boolean"
          },
          "api_key_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the API key that created the job, when one was used. `null` for dashboard runs."
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "`null` while the job is still running."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Error message when status is `failed`."
          }
        },
        "required": [
          "job_id",
          "status",
          "reference_url",
          "candidate_url",
          "source_url",
          "accepted_at"
        ]
      },
      "AdherenceJobList": {
        "type": "object",
        "properties": {
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdherenceJob"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total jobs matching the filter, ignoring pagination."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "jobs"
        ]
      },
      "AdherenceVerdict": {
        "type": "object",
        "description": "The verdict for a brand-adherence job: one score plus recommendations and fixes.",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/AdherenceStatus"
          },
          "reference_url": {
            "type": "string",
            "format": "uri"
          },
          "candidate_url": {
            "type": "string",
            "format": "uri"
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "deprecated": true,
            "description": "Deprecated mirror of candidate_url."
          },
          "is_public": {
            "type": "boolean"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "How well the source page follows the reference brand. `null` until the run has produced at least one scored component."
          },
          "recommendations": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Recommendation strings, worst-first, with exact target values. Capped at 20."
          },
          "fixes": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Structured fix objects, worst-first, capped at 20. Each carries an `action` discriminator (e.g. `snap_to_token`, `add_color_token`) plus fields specific to that action."
          }
        },
        "required": [
          "job_id",
          "status",
          "reference_url",
          "candidate_url",
          "source_url",
          "accepted_at",
          "score",
          "recommendations",
          "fixes"
        ]
      },
      "BrandKitAsset": {
        "type": [
          "object",
          "null"
        ],
        "description": "A file cut from the PDF. `null` when the book only describes the item in words.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted URL of the file."
          },
          "format": {
            "type": "string",
            "description": "`SVG` (vector from the PDF), `PNG` (render), or the embedded file's own type, such as `JPEG` or `TTF`."
          }
        }
      },
      "BrandKitColor": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "hex": {
            "type": "string",
            "description": "Hex color, such as `#3e6de8`."
          },
          "group": {
            "type": "string",
            "description": "The book's heading, such as primary, core, or secondary."
          },
          "cmyk": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            },
            "description": "CMYK as the book prints it. `null` when it prints none; never converted from the hex."
          },
          "rgb": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            },
            "description": "RGB as the book prints it. `null` when it prints none; never converted from the hex."
          },
          "proportion": {
            "type": "object",
            "properties": {
              "stated": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Share of use (0 to 1) the book prints in its text."
              },
              "drawn": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Share (0 to 1) the book's color page gives it, measured on the drawing. Kept even when it differs from `stated`."
              }
            },
            "description": "How much of the brand the color takes."
          },
          "when_to_use": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "When to use it, as guidance sentences."
          },
          "when_to_use_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "declared",
              "inferred",
              null
            ],
            "description": "`declared` when the book states the guidance, `inferred` when the engine wrote it."
          }
        }
      },
      "BrandKitButton": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "primary",
              "secondary",
              "tertiary",
              "text",
              "icon"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "default",
              "hover",
              "pressed",
              "disabled",
              "selected"
            ]
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every label the book draws in this style."
          },
          "background": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fill, named after the palette, such as `Volt #3e6de8`. `null` when outlined."
          },
          "text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "border_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "border_width_pt": {
            "type": "number"
          },
          "radius_pt": {
            "type": "number"
          },
          "padding_x_pt": {
            "type": "number"
          },
          "padding_y_pt": {
            "type": "number"
          },
          "height_pt": {
            "type": "number"
          },
          "font_family": {
            "type": "string"
          },
          "font_weight": {
            "type": "integer"
          },
          "font_size_pt": {
            "type": "number"
          },
          "case": {
            "type": "string",
            "enum": [
              "upper",
              "title",
              "sentence"
            ]
          },
          "css": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "The button as CSS, measured from the PDF (1pt = 4/3 px). Values the book states win."
          },
          "declared": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "CSS the book states for buttons, as written."
          },
          "asset": {
            "$ref": "#/components/schemas/BrandKitAsset"
          }
        },
        "description": "A button the book draws, measured from the PDF's vectors."
      },
      "BrandKitDesignSystem": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "brand_kit"
            ]
          },
          "profile": {
            "type": "object",
            "properties": {
              "brand_name": {
                "type": "string"
              },
              "tagline": {
                "type": "string"
              },
              "industry": {
                "type": "string"
              },
              "copy_tone": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "audience": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "values": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "personality": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "voice": {
                "type": "object",
                "additionalProperties": true,
                "description": "What the brand does and doesn't do in writing."
              },
              "messaging": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "summary": {
                "type": "string"
              },
              "inferred": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "personality",
                    "copy_tone",
                    "voice",
                    "audience"
                  ]
                },
                "description": "Profile fields the engine wrote because the book did not."
              }
            },
            "description": "Brand identity and voice."
          },
          "colors": {
            "type": "object",
            "properties": {
              "palette": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BrandKitColor"
                }
              },
              "pairs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "text": {
                      "type": "string",
                      "description": "Hex color, such as `#3e6de8`."
                    },
                    "background": {
                      "type": "string",
                      "description": "Hex color, such as `#3e6de8`."
                    },
                    "headers": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    },
                    "body": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    },
                    "cta": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    },
                    "contrast": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "WCAG 2 contrast ratio of text on background."
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "declared",
                        "measured"
                      ],
                      "description": "`declared`: the book approves it. `measured`: the book sets text this way."
                    }
                  }
                },
                "description": "Text and background combinations."
              },
              "proportion_sources": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "stated",
                        "drawn"
                      ]
                    },
                    "page": {
                      "type": "integer"
                    },
                    "boxes": {
                      "type": "array",
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "number"
                        }
                      },
                      "description": "Zones to look at, as `x0, y0, x1, y1` fractions of the page."
                    },
                    "asset": {
                      "$ref": "#/components/schemas/BrandKitAsset"
                    }
                  }
                },
                "description": "Where the book gives the proportions: each page and the zones on it."
              }
            }
          },
          "typography": {
            "type": "object",
            "additionalProperties": true,
            "description": "`typefaces` (family, roles, weights, licence), `hierarchy` (type levels with size, line height, unit, weight, and case), and `font_files` (fonts the PDF embeds). See [Typography](/concepts/brand-kit#typography)."
          },
          "logo": {
            "type": "object",
            "additionalProperties": true,
            "description": "`concept`, `variants` (arrangement, treatment, allowed backgrounds, asset), `construction` (clear space, minimum size), and `misuse` examples."
          },
          "photography": {
            "type": "object",
            "additionalProperties": true,
            "description": "`style` words and `examples` with a stance of `do`, `dont`, or `example`."
          },
          "graphics": {
            "type": "object",
            "additionalProperties": true,
            "description": "Graphic `elements`, `icons`, and the `icon_style`."
          },
          "applications": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "The brand applied to media, with a description and asset."
          },
          "layout": {
            "type": "object",
            "additionalProperties": true,
            "description": "`grids`, `spacing` rules, a measured `scale_pt`, and reusable `templates`."
          },
          "buttons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandKitButton"
            }
          },
          "cards": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Cards the book draws: the box, text levels in reading order, and `css`."
          },
          "motion": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "How the brand moves: `subject`, `behaviour`, `meaning`, and `use_cases`."
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "guideline": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The measurement or name the rule sets, when it has one."
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "do",
                    "dont",
                    "spec"
                  ]
                },
                "section": {
                  "type": "string",
                  "description": "`logo`, `colors`, `typography`, `photography`, `graphics`, `components`, or `voice`."
                }
              }
            },
            "description": "The book's own guidelines."
          }
        },
        "description": "The `result.design_system` of a brand kit submission. See [Brand kit](/concepts/brand-kit)."
      },
      "SubmissionKind": {
        "type": "string",
        "enum": [
          "website",
          "brand_kit",
          "photography"
        ],
        "description": "`website` for a URL extraction, `brand_kit` for a brand guidelines PDF, `photography` for a photography profile."
      },
      "BrandKitSectionName": {
        "type": "string",
        "description": "A top-level field of a brand kit's `result.design_system`, to filter a brand kit result.",
        "enum": [
          "profile",
          "logo",
          "colors",
          "typography",
          "photography",
          "graphics",
          "applications",
          "layout",
          "buttons",
          "cards",
          "motion",
          "rules"
        ]
      },
      "PhotoInput": {
        "type": "object",
        "description": "One photo: exactly one of `url` or `image_b64`.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 4096,
            "description": "Public `https://` URL of the photo. It is downloaded when the photo is read."
          },
          "image_b64": {
            "type": "string",
            "description": "The photo as base64: PNG, JPEG, or WebP, up to 64 megapixels."
          }
        },
        "oneOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "image_b64"
            ]
          }
        ],
        "additionalProperties": false
      },
      "PhotographySubmissionCreate": {
        "type": "object",
        "required": [
          "photos"
        ],
        "additionalProperties": false,
        "properties": {
          "photos": {
            "type": "array",
            "minItems": 1,
            "maxItems": 12,
            "items": {
              "$ref": "#/components/schemas/PhotoInput"
            },
            "description": "The brand's photos. From a brand kit, send the `do` and `example` assets of `photography.examples`."
          }
        }
      },
      "PhotoTrait": {
        "type": "object",
        "description": "A value most photos share.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Field id in the photography schema, such as `light_hardness`."
          },
          "name": {
            "type": "string",
            "description": "Readable field name."
          },
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ],
            "description": "The shared value. A list for fields that take several values."
          },
          "share": {
            "type": "number",
            "description": "Share of the merged photos that show this value, 0 to 1."
          }
        }
      },
      "PhotoVariation": {
        "type": "object",
        "description": "A field where no value is shared by enough photos to be a rule.",
        "properties": {
          "field": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "values": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Photos per value."
          }
        }
      },
      "PhotoTone": {
        "type": "object",
        "description": "A tone value measured from the pixels, never read by a model.",
        "properties": {
          "field": {
            "type": "string",
            "description": "Such as `exposure_key` or `highlight_rendering`."
          },
          "name": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "measure": {
            "type": "number",
            "description": "The median pixel measure the value was cut from."
          }
        }
      },
      "PhotoSignature": {
        "type": [
          "object",
          "null"
        ],
        "description": "How the photos render, as the median across photos. Lightness is CIE L*, 0 (black) to 100 (white); warmth is CIE b*, positive is warm (yellow), negative is cool (blue).",
        "properties": {
          "darkest_lightness": {
            "type": "number",
            "description": "L* of the darkest 0.5% of pixels. Near 0 is a true black."
          },
          "brightest_lightness": {
            "type": "number",
            "description": "L* of the brightest 0.5% of pixels. Near 100 is a true white."
          },
          "clipped_highlights_percent": {
            "type": "number",
            "description": "Percent of pixels blown to white."
          },
          "midtone_warmth": {
            "type": [
              "number",
              "null"
            ],
            "description": "b* of the mid-tones (L* 30 to 80)."
          },
          "shadow_warmth": {
            "type": "number",
            "description": "b* of the darkest 5% of pixels."
          },
          "colourfulness": {
            "type": "number",
            "description": "Hasler-Süsstrunk colourfulness: under 15 is near grey, 33 to 45 moderate, over 80 vivid."
          },
          "lightness_spread": {
            "type": "number",
            "description": "Standard deviation of L*. Higher is more contrast."
          }
        }
      },
      "PhotographyProfile": {
        "type": [
          "object",
          "null"
        ],
        "description": "What the brand's photos look like. `null` until the submission completes.",
        "properties": {
          "id": {
            "type": "string"
          },
          "schema_version": {
            "type": "string",
            "description": "Version of the photography schema the photos were read against."
          },
          "photos": {
            "type": "integer",
            "description": "Photos merged into the profile, after graphics, renders, and composites are left out."
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhotoTrait"
            },
            "description": "Values most of the merged photos share. With one photo, every value it shows."
          },
          "varies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhotoVariation"
            }
          },
          "tone": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhotoTone"
            }
          },
          "signature": {
            "$ref": "#/components/schemas/PhotoSignature"
          },
          "palette": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Dominant colors of the photos as hex. These are not brand colors."
          }
        }
      },
      "PhotoOutcome": {
        "type": "object",
        "description": "What became of one photo, in the order sent.",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Position of the photo in the request, from 1."
          },
          "status": {
            "type": "string",
            "enum": [
              "read",
              "invalid",
              "failed"
            ],
            "description": "`read`: the photo was read. `invalid`: it cannot be used (unreachable URL, not an image, too small or too large). `failed`: reading it failed after retries."
          },
          "main_subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the photo shows, as read."
          },
          "excluded": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "graphic_or_illustration",
              "photoreal_render",
              "composite_or_manipulated",
              null
            ],
            "description": "Why a read photo was left out of the profile. `null` when it was merged."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PhotographyResult": {
        "type": "object",
        "description": "The result of a photography submission.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "photography"
            ]
          },
          "id": {
            "type": "string",
            "description": "The submission ID."
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "photos": {
            "type": "integer",
            "description": "Photos sent."
          },
          "profile": {
            "$ref": "#/components/schemas/PhotographyProfile"
          },
          "outcomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhotoOutcome"
            }
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      }
    }
  }
}
