{
  "openapi": "3.1.0",
  "info": {
    "title": "HungryShot Public API",
    "version": "1.1",
    "description": "The HungryShot Public API lets Expert Pro customers call HungryShot's AI food-photo tools\n(Ad Crafter, Beautifier, Recolor, Photo Shoots, Video, Chefs & Diners) programmatically, using the same\npipelines as the HungryShot Studio. It is built for restaurants, cafés, bars, bakeries, cloud kitchens,\nfood trucks, caterers and the agencies that work for them.\n\n## Tools\n- **Ad Crafter** (`POST /ad-crafter`): dish name, menu description, short and long caption, hashtags,\n  an allergen-aware note (only echoes the dietary tags you send) and a styled dish image, tailored to a\n  destination: Uber Eats, DoorDash, Deliveroo, Just Eat, Glovo, Instagram, TikTok, Facebook,\n  Google Business Profile, a printed/online menu or other. Options: `GET /ad-crafter/options`.\n- **Beautifier** (`POST /beautifier`): phone photo of a dish → studio-quality food photo (camera angle + surface).\n- **Recolor** (`POST /recoloration`): recolor tableware, packaging, napkins, cups, drink colour or signage\n  to a brand colour. The food itself is never recolored.\n- **Photo Shoots** (`POST /photoshoots`): styled food shoot of a saved dish, optionally with a person.\n- **Video** (`POST /video`): short dish clip with a motion (steam rise, cheese pull, sauce pour,\n  slow turntable…). Motions: `GET /video/motions`.\n- **Chefs & Diners** (`POST /models`, list with `GET /models`): reusable generated people (chef, barista,\n  bartender, server, diner, hands only). Route names keep `models` for compatibility.\n\nStable ids for categories, camera angles, surfaces, people and motions: `GET /catalog`. Tools, modes and\ncredit costs: `GET /tools`.\n\n## Accuracy rules\nGeneration preserves the real dish (same ingredients, portion, colours, toppings) and never invents\nallergen, nutrition, halal/kosher, price or origin claims. No text or logos are rendered into images\nunless asked.\n\n## Plan requirement\nEvery endpoint requires an API key belonging to a user on the **Expert Pro** plan.\nKeys are created from Studio → Settings → API Keys and are only usable while the\nowning account remains on Expert Pro; if the account is downgraded, existing keys\nstop working (`403 forbidden`) even though the key itself is still active.\n\n## Shared credit pool\nAPI usage draws from the **same credit pool** as the Studio. There is no separate API\ncredit allowance, and a failed generation is refunded automatically. Check the balance with `GET /credits`.\n\n## Sync vs. async tools\n- **Sync** (`POST /ad-crafter`, `POST /beautifier`, `POST /recoloration`) run the full generation\n  inline and return the finished result with `200 OK`.\n- **Async** (`POST /photoshoots`, `POST /video`, `POST /models`) validate the request, deduct credits\n  and enqueue the job, returning `202 Accepted` with a `job_id`. Poll `GET /jobs/{id}` until `status`\n  is `completed` or `failed`; `GET /jobs` lists recent jobs.\n\nAll responses (success and error) are wrapped in the same `{ data, error }` envelope (see\n`components.schemas.Envelope`). Errors carry a machine-readable `code`, a `message` and a resolution\n`hint`. Unknown `/api/*` paths also answer with this JSON envelope (`404 not_found`), never an HTML page.\n\n## Rate limits\nEvery API response carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (seconds) and\n`RateLimit-Policy` headers (IETF `ratelimit-headers` draft). The default allowance is 60 requests per\nminute per key. A `429` also carries `Retry-After`.\n\n## Errors\nEvery 4xx/5xx response from `/api/v1` has the typed shape `ErrorEnvelope`\n(`{ \"data\": null, \"error\": { \"code\", \"message\", \"hint\", \"docs_url\" } }`). `code` is one of a closed enum,\nso clients can branch on it without parsing prose.\n\n## Versioning and deprecation\nThe major version is part of the URL (`/api/v1`) and echoed in the `API-Version` response header.\nWithin a major version we only add optional request fields, response fields and endpoints. A breaking\nchange ships as a new major version; the old one keeps working during a sunset window. A deprecated\nendpoint or field answers with a `Deprecation` header (RFC 9745) and a `Sunset` header (RFC 8594,\nthe removal date) for at least 6 months before it is removed.\n\nDeprecated today (fields only, no endpoint): the Ad Crafter response aliases `listing_title`,\n`listing_description`, `listing_hashtags`, `detected_brand`, `detected_condition`; the request alias\n`platform` (use `destination`). Legacy category and platform ids from before the food release are\naccepted and mapped to a default for one release.\n\n## Studio-only tools\nCampaign Composer (`/studio/editorial`) and Detail Repair (`/studio/detail-repair`) need a signed-in\nStudio session and are not part of the `/api/v1` contract yet.\n\n## Tooling\nA command-line client is published as `foodyshot-cli` (see https://hungryshot.com/developers).\n",
    "contact": {
      "name": "HungryShot",
      "url": "https://hungryshot.com",
      "email": "contact@hungryshot.com"
    }
  },
  "servers": [
    {
      "url": "https://hungryshot.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Account and credit balance introspection."
    },
    {
      "name": "Catalog",
      "description": "Stable ids and options used to build generation requests."
    },
    {
      "name": "Sync tools",
      "description": "Generation endpoints that return the finished result directly."
    },
    {
      "name": "Async tools",
      "description": "Generation endpoints that enqueue a background job and return 202."
    },
    {
      "name": "Jobs",
      "description": "Poll or list async jobs."
    },
    {
      "name": "People",
      "description": "Saved generated people (Chefs & Diners)."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Get the authenticated account's plan and API key metadata",
        "description": "Returns the plan of the account that owns the API key plus the key's name, prefix and created/expiry timestamps. Use it to verify a key works before calling generation endpoints.",
        "responses": {
          "200": {
            "description": "Account and key info.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Me"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": {
                    "plan": "expert_pro",
                    "key_name": "Kitchen automation",
                    "key_prefix": "psl_live_ab12",
                    "created_at": "2026-10-01T09:00:00Z",
                    "expires_at": null
                  },
                  "error": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/credits": {
      "get": {
        "operationId": "getCredits",
        "tags": [
          "Account"
        ],
        "summary": "Get remaining credit balance",
        "description": "Returns the account's current credit balance. This is the same shared pool used by the Studio; there is no separate API allowance.",
        "responses": {
          "200": {
            "description": "Remaining credits.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Credits"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": {
                    "credits_remaining": 420
                  },
                  "error": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/tools": {
      "get": {
        "operationId": "listTools",
        "tags": [
          "Catalog"
        ],
        "summary": "List the generation tools, their mode and credit cost",
        "description": "Returns every generation tool with its endpoint, sync/async mode and credit cost per result. Video is priced per second of output (`video_credits_per_second`).",
        "responses": {
          "200": {
            "description": "Tools.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ToolList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/catalog": {
      "get": {
        "operationId": "getCatalog",
        "tags": [
          "Catalog"
        ],
        "summary": "Get the shared food vocabulary (categories, angles, surfaces, people, motions)",
        "description": "Stable ids accepted by the generation endpoints: dish categories, camera angles, surface categories, courses, people groups and roles, attire, recolor targets and video motions.",
        "responses": {
          "200": {
            "description": "Catalog.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Catalog"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/ad-crafter": {
      "post": {
        "operationId": "createAdCrafterListing",
        "tags": [
          "Sync tools"
        ],
        "summary": "Write restaurant copy and style the dish photo (Ad Crafter)",
        "description": "Synchronous. Analyses the dish photo, writes the dish name, menu description, short and long caption and hashtags for the chosen destination (respecting its length and hashtag rules), builds an allergen-aware note that only echoes the dietary tags you send, and renders a styled dish image. 10 credits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdCrafterRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Restaurant copy + styled image.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/AdCrafterResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/ad-crafter/options": {
      "get": {
        "operationId": "getAdCrafterOptions",
        "tags": [
          "Catalog"
        ],
        "summary": "List Ad Crafter destinations, copy limits, languages, tones and dietary tags",
        "description": "Returns each destination with its copy limits (dish name, menu description, captions, hashtag count) plus the accepted languages, tones, dietary tags, currencies and presentations.",
        "responses": {
          "200": {
            "description": "Ad Crafter options.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/AdCrafterOptions"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/beautifier": {
      "post": {
        "operationId": "createBeautifierImage",
        "tags": [
          "Sync tools"
        ],
        "summary": "Turn a phone photo of a dish into a studio-quality food photo (Beautifier)",
        "description": "Synchronous. Re-shoots the dish with the chosen camera angle, presentation and surface while preserving the real dish. 10 credits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BeautifierRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated image.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ImageResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/recoloration": {
      "post": {
        "operationId": "createRecoloration",
        "tags": [
          "Sync tools"
        ],
        "summary": "Recolor tableware, packaging or a drink to a brand colour (Recolor)",
        "description": "Synchronous. Recolors the chosen target (plate, packaging, napkin, cup, drink colour or signage) to a hex colour. The food itself is never recolored. 10 credits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecolorationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recolored image.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RecolorationResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/photoshoots": {
      "post": {
        "operationId": "createPhotoshoot",
        "tags": [
          "Async tools"
        ],
        "summary": "Start a styled food photo shoot of a saved dish (Photo Shoots)",
        "description": "Asynchronous. Validates the request, deducts 10 credits and enqueues the shoot; poll `GET /jobs/{id}` for the result. The dish must exist in the account's Dishes library (`productId`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhotoshootRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/video": {
      "post": {
        "operationId": "createVideo",
        "tags": [
          "Async tools"
        ],
        "summary": "Start a short dish video with a motion (Video)",
        "description": "Asynchronous. Enqueues a Kling 4.0 clip of a saved dish performing a motion (steam rise, cheese pull, sauce pour…). Send `gestureId` (or `motionId`) with a catalog motion id; label and prompt default to the catalog values. Credits = duration × per-second price for the resolution. Poll `GET /jobs/{id}`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VideoRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/video/motions": {
      "get": {
        "operationId": "listVideoMotions",
        "tags": [
          "Catalog"
        ],
        "summary": "List video motions with their default prompts and poster frames",
        "description": "Returns the 16 food motions (id, label, description, default prompt, poster URL, whether a person is needed) plus accepted durations, aspect ratios, resolutions and subject-focus options.",
        "responses": {
          "200": {
            "description": "Motions.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/MotionList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/models": {
      "post": {
        "operationId": "createModel",
        "tags": [
          "Async tools"
        ],
        "summary": "Generate a reusable person: chef, barista, bartender, server, diner or hands (Chefs & Diners)",
        "description": "Asynchronous. Generates a photorealistic generic person (never a real or famous person) that can be reused in Photo Shoots and Video. 10 credits. Poll `GET /jobs/{id}`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/JobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      },
      "get": {
        "operationId": "listPeople",
        "tags": [
          "People"
        ],
        "summary": "List saved people (Chefs & Diners)",
        "description": "Returns the account's completed, non-archived generated people, newest first. Use `id` as `modelId` and `image_url` as `modelImageUrl` in Photo Shoots and Video.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return (1-100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saved people.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PersonList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/jobs": {
      "get": {
        "operationId": "listJobs",
        "tags": [
          "Jobs"
        ],
        "summary": "List recent async jobs",
        "description": "Returns the account's most recent photo shoot, video and person jobs, newest first, with the same status mapping as `GET /jobs/{id}`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return (1-100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent jobs.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/JobList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "tags": [
          "Jobs"
        ],
        "summary": "Get the status and result of an async job",
        "description": "Looks the job up across photo shoots, videos and people. `result.url` is set once `status` is `completed`; `error.message` once it is `failed` (credits are refunded automatically).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The `job_id` returned by the original POST.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Job"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": {
                    "job_id": "b3b9e2b0-3b7a-4e9e-9b0e-2b3b7a4e9e9b",
                    "status": "completed",
                    "result": {
                      "url": "https://cdn.hungryshot.com/results/u/b3b9_video.mp4"
                    },
                    "error": null
                  },
                  "error": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "RateLimitLimit": {
        "description": "Requests allowed per window for this API key (`RateLimit-Policy` gives the window, 60 seconds).",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer",
          "example": 59
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current window resets.",
        "schema": {
          "type": "integer",
          "example": 42
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying (sent with `429`).",
        "schema": {
          "type": "integer",
          "example": 42
        }
      },
      "RateLimitPolicy": {
        "description": "Quota policy as `<limit>;w=<window seconds>`, e.g. `60;w=60`.",
        "schema": {
          "type": "string",
          "example": "60;w=60"
        }
      },
      "ApiVersion": {
        "description": "Major API version that served the request (matches the `/v1` URL prefix).",
        "schema": {
          "type": "string",
          "example": "1"
        }
      },
      "Deprecation": {
        "description": "RFC 9745 deprecation signal: `@<unix timestamp>` from which the endpoint is deprecated. Only present on deprecated endpoints; see Versioning and deprecation.",
        "schema": {
          "type": "string",
          "example": "@1798761600"
        }
      },
      "Sunset": {
        "description": "RFC 8594 HTTP date after which the endpoint stops working. Only present on deprecated endpoints, together with `Deprecation`.",
        "schema": {
          "type": "string",
          "example": "Fri, 01 Jan 2027 00:00:00 GMT"
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key issued from Studio → Settings → API Keys, in the form `psl_live_<random>`. Send it as `Authorization: Bearer psl_live_<random>`. Keys are only valid while the owning account is on the Expert Pro plan."
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "required": [
          "data",
          "error"
        ],
        "description": "Every response is wrapped in this envelope. On success `error` is null.",
        "properties": {
          "data": {
            "description": "Endpoint-specific payload, null on error."
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ApiError"
              }
            ]
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "invalid_request",
              "unauthorized",
              "insufficient_credits",
              "forbidden",
              "not_found",
              "rate_limited",
              "internal_error",
              "upstream_error"
            ],
            "description": "Machine-readable error code."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation."
          },
          "hint": {
            "type": "string",
            "description": "One-line resolution hint an agent can act on."
          },
          "docs_url": {
            "type": "string",
            "format": "uri",
            "example": "https://hungryshot.com/api-docs"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "data",
          "error"
        ],
        "properties": {
          "data": {
            "type": "null"
          },
          "error": {
            "$ref": "#/components/schemas/ApiError"
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "plan": {
            "type": [
              "string",
              "null"
            ],
            "example": "expert_pro"
          },
          "key_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "key_prefix": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Credits": {
        "type": "object",
        "required": [
          "credits_remaining"
        ],
        "properties": {
          "credits_remaining": {
            "type": "number",
            "example": 420
          }
        }
      },
      "ToolList": {
        "type": "object",
        "properties": {
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "endpoint",
                "mode"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "ad-crafter",
                    "beautifier",
                    "recoloration",
                    "photoshoots",
                    "video",
                    "models"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "endpoint": {
                  "type": "string",
                  "example": "POST /ad-crafter"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "sync",
                    "async"
                  ]
                },
                "credits": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Credits per result; null when priced per second (video)."
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "video_credits_per_second": {
            "type": "object",
            "properties": {
              "720p": {
                "type": "integer"
              },
              "1080p": {
                "type": "integer"
              }
            }
          }
        }
      },
      "IdLabel": {
        "type": "object",
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        }
      },
      "Catalog": {
        "type": "object",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "burgers",
                    "pizza",
                    "asian",
                    "grill",
                    "salads",
                    "bakery",
                    "desserts",
                    "drinks"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "examples": {
                  "type": "string"
                },
                "image_url": {
                  "type": "string"
                },
                "default_angle": {
                  "type": "string"
                }
              }
            }
          },
          "camera_angles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "requires_person": {
                  "type": "boolean"
                },
                "image_url": {
                  "type": "string"
                }
              }
            }
          },
          "surface_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "surfaces": {
            "type": "array",
            "description": "Surface / setting ids (kebab-case, prefixed by category) accepted as `backgroundId`, `background_id` and `background_value`.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "flatlay-white-marble"
                },
                "label": {
                  "type": "string"
                },
                "category": {
                  "type": "string",
                  "enum": [
                    "studio",
                    "flatlay",
                    "outdoor",
                    "indoor",
                    "lifestyle"
                  ]
                },
                "image_url": {
                  "type": "string"
                }
              }
            }
          },
          "courses": {
            "type": "array",
            "description": "Dish courses (`products.course`): mains, sides, desserts, drinks.",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "people_groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "person_roles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "chef",
                    "barista",
                    "bartender",
                    "server",
                    "diner",
                    "hands"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "group": {
                  "type": "string"
                }
              }
            }
          },
          "attire": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "auto",
                "chef-whites",
                "black-chef-jacket",
                "denim-apron",
                "linen-apron",
                "leather-apron",
                "bartender-vest",
                "server-black",
                "casual-smart",
                "casual-relaxed",
                "summer-light",
                "knitwear"
              ]
            }
          },
          "recolor_targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "motions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "steam-rise",
                    "cheese-pull",
                    "sauce-pour",
                    "drizzle",
                    "sizzle",
                    "slow-turntable",
                    "slice-reveal",
                    "ice-splash",
                    "cocktail-pour",
                    "bubble-fizz",
                    "crumb-fall",
                    "camera-push-in",
                    "hand-grab",
                    "cheers-clink",
                    "flame-sear",
                    "dust-sugar"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "needs_person": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "Motion": {
        "type": "object",
        "required": [
          "id",
          "label",
          "prompt",
          "poster_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "steam-rise",
              "cheese-pull",
              "sauce-pour",
              "drizzle",
              "sizzle",
              "slow-turntable",
              "slice-reveal",
              "ice-splash",
              "cocktail-pour",
              "bubble-fizz",
              "crumb-fall",
              "camera-push-in",
              "hand-grab",
              "cheers-clink",
              "flame-sear",
              "dust-sugar"
            ]
          },
          "label": {
            "type": "string",
            "example": "Cheese pull"
          },
          "description": {
            "type": "string"
          },
          "prompt": {
            "type": "string",
            "description": "Default motion direction; send it (edited or not) as `gesturePrompt`."
          },
          "poster_url": {
            "type": "string",
            "example": "/assets/food/motions/cheese-pull.webp"
          },
          "needs_person": {
            "type": "boolean",
            "description": "True when hands or a person appear (subject focus applies)."
          },
          "best_for": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "burgers",
                "pizza",
                "asian",
                "grill",
                "salads",
                "bakery",
                "desserts",
                "drinks"
              ]
            }
          }
        }
      },
      "MotionList": {
        "type": "object",
        "properties": {
          "motions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Motion"
            }
          },
          "durations": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "example": [
              3,
              5,
              8,
              10,
              15,
              20,
              30
            ]
          },
          "aspect_ratios": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "9:16",
              "16:9",
              "1:1",
              "21:9"
            ]
          },
          "resolutions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "720p",
              "1080p"
            ]
          },
          "subject_focus": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AdCrafterOptions": {
        "type": "object",
        "properties": {
          "destinations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "ubereats",
                    "doordash",
                    "deliveroo",
                    "justeat",
                    "glovo",
                    "instagram",
                    "tiktok",
                    "facebook",
                    "google",
                    "menu",
                    "other"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "delivery",
                    "social",
                    "local",
                    "menu",
                    "generic"
                  ]
                },
                "color": {
                  "type": "string",
                  "example": "#06C167"
                },
                "limits": {
                  "type": "object",
                  "properties": {
                    "dish_name": {
                      "type": "integer"
                    },
                    "menu_description": {
                      "type": "integer"
                    },
                    "short_caption": {
                      "type": "integer"
                    },
                    "long_caption": {
                      "type": "integer"
                    },
                    "hashtags_min": {
                      "type": "integer"
                    },
                    "hashtags_max": {
                      "type": "integer"
                    }
                  }
                },
                "emojis": {
                  "type": "boolean"
                }
              }
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "tones": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "mouthwatering",
                    "premium",
                    "playful",
                    "healthy",
                    "street"
                  ]
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "dietary_tags": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "vegan",
                    "vegetarian",
                    "halal",
                    "kosher",
                    "gluten-free",
                    "dairy-free",
                    "spicy",
                    "nut-free"
                  ]
                },
                "label": {
                  "type": "string"
                }
              }
            }
          },
          "currencies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "EUR",
                "USD",
                "GBP",
                "CHF",
                "CAD",
                "AUD",
                "PLN",
                "SEK",
                "NOK",
                "DKK",
                "MAD",
                "AED",
                "SAR",
                "TRY",
                "BRL",
                "MXN",
                "JPY",
                "INR"
              ]
            }
          },
          "presentations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdLabel"
            }
          },
          "background_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ImageInput": {
        "type": "string",
        "description": "Source photo as an HTTPS URL or a base64 data URI. Resolved server-side through an SSRF-safe fetcher."
      },
      "AdCrafterRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "image"
        ],
        "properties": {
          "image": {
            "$ref": "#/components/schemas/ImageInput"
          },
          "destination": {
            "type": "string",
            "enum": [
              "ubereats",
              "doordash",
              "deliveroo",
              "justeat",
              "glovo",
              "instagram",
              "tiktok",
              "facebook",
              "google",
              "menu",
              "other"
            ],
            "default": "other",
            "description": "Where the copy will be published."
          },
          "platform": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ubereats",
              "doordash",
              "deliveroo",
              "justeat",
              "glovo",
              "instagram",
              "tiktok",
              "facebook",
              "google",
              "menu",
              "other",
              null
            ],
            "deprecated": true,
            "description": "Deprecated alias of `destination`. Legacy resale ids are mapped to `other`."
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "es",
              "de",
              "it",
              "nl",
              "pt",
              "pl"
            ],
            "default": "en"
          },
          "cuisine": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "example": "Neapolitan"
          },
          "tone": {
            "type": "string",
            "enum": [
              "mouthwatering",
              "premium",
              "playful",
              "healthy",
              "street"
            ],
            "default": "mouthwatering"
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100000,
            "description": "Optional. Only used to format `price_label`; never invented."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "EUR",
              "USD",
              "GBP",
              "CHF",
              "CAD",
              "AUD",
              "PLN",
              "SEK",
              "NOK",
              "DKK",
              "MAD",
              "AED",
              "SAR",
              "TRY",
              "BRL",
              "MXN",
              "JPY",
              "INR",
              null
            ]
          },
          "dietary_tags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "vegan",
                "vegetarian",
                "halal",
                "kosher",
                "gluten-free",
                "dairy-free",
                "spicy",
                "nut-free"
              ]
            },
            "uniqueItems": true,
            "description": "Ticked by the restaurant. The allergen note only ever echoes these."
          },
          "dish_name_hint": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "The restaurant's own name for the dish, if any."
          },
          "custom_instructions": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Extra context. Wrapped in an injection-safe block: treated as data, never as instructions."
          },
          "background_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "solid",
              "gradient",
              "carpet",
              "material",
              "lifestyle",
              null
            ],
            "default": "solid"
          },
          "background_value": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "Surface id (kebab-case, e.g. `studio-terracotta`) or colour swatch id."
          },
          "mannequin_type": {
            "type": "string",
            "enum": [
              "none",
              "invisible",
              "virtual_model",
              "mirror"
            ],
            "default": "none",
            "description": "Presentation: none = dish only, invisible = clean cut-out, virtual_model = generic hands serving, mirror = phone-shot UGC style."
          }
        }
      },
      "AdCrafterResult": {
        "type": "object",
        "required": [
          "generation_id",
          "destination",
          "dish_name",
          "menu_description",
          "short_caption",
          "long_caption",
          "hashtags",
          "allergen_note"
        ],
        "properties": {
          "generation_id": {
            "type": "string"
          },
          "result_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_enhancement_failed": {
            "type": "boolean"
          },
          "destination": {
            "type": "string",
            "enum": [
              "ubereats",
              "doordash",
              "deliveroo",
              "justeat",
              "glovo",
              "instagram",
              "tiktok",
              "facebook",
              "google",
              "menu",
              "other"
            ]
          },
          "dish_name": {
            "type": "string",
            "example": "Margherita with fior di latte"
          },
          "menu_description": {
            "type": "string"
          },
          "short_caption": {
            "type": "string"
          },
          "long_caption": {
            "type": "string"
          },
          "hashtags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "allergen_note": {
            "type": "string",
            "description": "Built only from `dietary_tags`; empty when none were sent."
          },
          "dietary_tags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "vegan",
                "vegetarian",
                "halal",
                "kosher",
                "gluten-free",
                "dairy-free",
                "spicy",
                "nut-free"
              ]
            }
          },
          "price_label": {
            "type": [
              "string",
              "null"
            ],
            "example": "€12.50"
          },
          "detected_cuisine": {
            "type": [
              "string",
              "null"
            ]
          },
          "detected_course": {
            "type": [
              "string",
              "null"
            ]
          },
          "processing_time_ms": {
            "type": "number"
          },
          "listing_title": {
            "type": "string",
            "deprecated": true,
            "description": "Deprecated alias of `dish_name`."
          },
          "listing_description": {
            "type": "string",
            "deprecated": true,
            "description": "Deprecated alias of `menu_description`."
          },
          "listing_hashtags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "deprecated": true,
            "description": "Deprecated alias of `hashtags`."
          },
          "detected_brand": {
            "type": "null",
            "deprecated": true
          },
          "detected_condition": {
            "type": "null",
            "deprecated": true
          }
        }
      },
      "BeautifierRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "image",
          "category",
          "angle"
        ],
        "properties": {
          "image": {
            "$ref": "#/components/schemas/ImageInput"
          },
          "category": {
            "type": "string",
            "enum": [
              "burgers",
              "pizza",
              "asian",
              "grill",
              "salads",
              "bakery",
              "desserts",
              "drinks"
            ]
          },
          "angle": {
            "type": "string",
            "enum": [
              "overhead",
              "high-45",
              "three-quarter",
              "eye-level",
              "low-hero",
              "macro",
              "side-cut",
              "diagonal",
              "dutch",
              "tray-flatlay",
              "negative-space",
              "hero-centered"
            ]
          },
          "shotType": {
            "type": "string",
            "enum": [
              "plated",
              "menu-cutout",
              "takeaway",
              "styled-scene"
            ],
            "default": "plated",
            "description": "Presentation of the dish."
          },
          "backgroundId": {
            "type": "string",
            "maxLength": 100,
            "default": "",
            "description": "Surface id, kebab-case (e.g. `flatlay-white-marble`, see `GET /catalog`); empty for the default."
          },
          "aspectRatio": {
            "type": "string",
            "maxLength": 10,
            "default": "1:1"
          }
        }
      },
      "ImageResult": {
        "type": "object",
        "required": [
          "generation_id"
        ],
        "properties": {
          "generation_id": {
            "type": "string"
          },
          "result_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "processing_time_ms": {
            "type": "number"
          }
        }
      },
      "RecolorationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "image",
          "hex"
        ],
        "properties": {
          "image": {
            "$ref": "#/components/schemas/ImageInput"
          },
          "hex": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$",
            "example": "#F97316"
          },
          "color_name": {
            "type": "string",
            "maxLength": 100,
            "default": "Custom Color"
          },
          "recolor_target": {
            "type": "string",
            "enum": [
              "tableware",
              "packaging",
              "napkin",
              "cup",
              "drink",
              "signage"
            ],
            "description": "What to recolor. The food itself is never recolored. Omit to let the model pick the main vessel."
          },
          "target": {
            "type": "string",
            "enum": [
              "tableware",
              "packaging",
              "napkin",
              "cup",
              "drink",
              "signage"
            ],
            "deprecated": true,
            "description": "Alias of `recolor_target`."
          },
          "custom_instructions": {
            "type": "string",
            "maxLength": 500,
            "default": ""
          },
          "aspect_ratio": {
            "type": "string",
            "default": "1:1"
          },
          "background_id": {
            "type": "string",
            "maxLength": 100,
            "default": "",
            "description": "Surface id (e.g. `studio-warm-cream`); empty keeps the original setting."
          }
        }
      },
      "RecolorationResult": {
        "type": "object",
        "required": [
          "generation_id",
          "hex"
        ],
        "properties": {
          "generation_id": {
            "type": "string"
          },
          "result_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "color_name": {
            "type": "string"
          },
          "hex": {
            "type": "string"
          },
          "processing_time_ms": {
            "type": "number"
          }
        }
      },
      "PhotoshootRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "category",
          "productId"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 100,
            "default": "Untitled shoot"
          },
          "category": {
            "type": "string",
            "enum": [
              "burgers",
              "pizza",
              "asian",
              "grill",
              "salads",
              "bakery",
              "desserts",
              "drinks"
            ]
          },
          "productId": {
            "type": "string",
            "format": "uuid",
            "description": "Dish id from the Dishes library (dishes have a `course`: mains, sides, desserts, drinks)."
          },
          "modelId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional person: curated id `<group>_<nn>` (chefs, bar-cafe, diners, hands, e.g. `chefs_01`) or a saved person id from `GET /models`."
          },
          "modelLabel": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "modelImageUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person image as HTTPS URL or base64 data URI."
          },
          "angleId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Camera angle id (see `GET /catalog`)."
          },
          "angleLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "stylingId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Prop id."
          },
          "stylingLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "stylingAssetId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "stylingThumbPath": {
            "type": [
              "string",
              "null"
            ]
          },
          "angleAssetId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "backgroundId": {
            "type": "string",
            "maxLength": 100,
            "default": "studio-warm-cream",
            "description": "Surface / setting id, kebab-case (see `GET /catalog`)."
          },
          "backgroundLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "aspectRatio": {
            "type": "string",
            "default": "1:1"
          }
        }
      },
      "VideoRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "category",
          "productId"
        ],
        "description": "Send `gestureId` or `motionId`. With a catalog motion id, label and prompt are optional.",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 100,
            "default": "Untitled video"
          },
          "category": {
            "type": "string",
            "enum": [
              "burgers",
              "pizza",
              "asian",
              "grill",
              "salads",
              "bakery",
              "desserts",
              "drinks"
            ]
          },
          "productId": {
            "type": "string",
            "format": "uuid",
            "description": "Dish id from the Dishes library."
          },
          "modelId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional person: curated id `<group>_<nn>` (e.g. `hands_01`) or a saved person id."
          },
          "modelLabel": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "modelImageUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person image as HTTPS URL or base64 data URI."
          },
          "gestureId": {
            "type": "string",
            "description": "Motion id, e.g. `cheese-pull` (see `GET /video/motions`).",
            "examples": [
              "steam-rise",
              "cheese-pull",
              "sauce-pour"
            ]
          },
          "gestureLabel": {
            "type": "string",
            "maxLength": 100
          },
          "gesturePrompt": {
            "type": "string",
            "maxLength": 500,
            "description": "Motion direction; treated as a description of movement only."
          },
          "motionId": {
            "type": "string",
            "description": "Alias of `gestureId`."
          },
          "motionLabel": {
            "type": "string",
            "maxLength": 100,
            "description": "Alias of `gestureLabel`."
          },
          "motionPrompt": {
            "type": "string",
            "maxLength": 500,
            "description": "Alias of `gesturePrompt`."
          },
          "stylingId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Prop id."
          },
          "stylingLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "stylingAssetId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "backgroundId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Surface / setting id, kebab-case (e.g. `flatlay-light-oak`)."
          },
          "backgroundLabel": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration": {
            "type": "integer",
            "minimum": 3,
            "maximum": 30,
            "default": 5
          },
          "aspectRatio": {
            "type": "string",
            "default": "9:16",
            "enum": [
              "9:16",
              "16:9",
              "1:1",
              "21:9"
            ]
          },
          "mood": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "subjectFocus": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Where the person looks; only used when a person is in frame."
          },
          "customInstruction": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Wrapped in an injection-safe block."
          }
        }
      },
      "PersonRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "gender",
          "age",
          "ethnicity",
          "morphology",
          "hairStyle",
          "hairColor"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "chef",
              "barista",
              "bartender",
              "server",
              "diner",
              "hands"
            ],
            "default": "chef"
          },
          "attire": {
            "type": "string",
            "enum": [
              "auto",
              "chef-whites",
              "black-chef-jacket",
              "denim-apron",
              "linen-apron",
              "leather-apron",
              "bartender-vest",
              "server-black",
              "casual-smart",
              "casual-relaxed",
              "summer-light",
              "knitwear"
            ],
            "default": "auto"
          },
          "gender": {
            "type": "string",
            "enum": [
              "female",
              "male",
              "other"
            ]
          },
          "age": {
            "type": "integer",
            "minimum": 18,
            "maximum": 80
          },
          "ethnicity": {
            "type": "string",
            "enum": [
              "caucasian",
              "asian",
              "african",
              "hispanic",
              "middle-eastern",
              "north-african"
            ]
          },
          "morphology": {
            "type": "string",
            "enum": [
              "standard",
              "slim",
              "athletic",
              "plus-size"
            ]
          },
          "eyeColor": {
            "type": "string",
            "enum": [
              "blue",
              "green",
              "brown",
              "hazel",
              "gray",
              "amber"
            ],
            "default": "brown"
          },
          "eyeShape": {
            "type": "string",
            "enum": [
              "almond",
              "round",
              "monolid",
              "hooded",
              "downturned",
              "upturned"
            ],
            "default": "almond"
          },
          "skinTone": {
            "type": "string",
            "enum": [
              "fair",
              "light",
              "medium",
              "tan",
              "deep",
              "rich"
            ],
            "default": "medium"
          },
          "skinUndertone": {
            "type": "string",
            "enum": [
              "cool",
              "neutral",
              "warm",
              "olive"
            ],
            "default": "neutral"
          },
          "hairStyle": {
            "type": "string",
            "enum": [
              "long-wavy",
              "long-straight",
              "short-curly",
              "short-straight",
              "buzzcut",
              "bun",
              "bald",
              "medium-curly",
              "braids",
              "locs",
              "pixie",
              "ponytail",
              "afro",
              "taper-fade",
              "crew-cut",
              "textured-crop",
              "side-part",
              "quiff",
              "slick-back"
            ]
          },
          "hairColor": {
            "type": "string",
            "enum": [
              "blonde",
              "brown",
              "black",
              "redhead",
              "gray",
              "dark-blonde",
              "light-brown",
              "dark-brown",
              "platinum",
              "auburn",
              "strawberry-blonde",
              "salt-and-pepper"
            ]
          },
          "distinctiveTrait": {
            "type": "string",
            "enum": [
              "none",
              "freckles",
              "bleach-eyebrows",
              "heterochromia",
              "vitiligo",
              "big-beard"
            ],
            "default": "none"
          },
          "customInstructions": {
            "type": "string",
            "maxLength": 500,
            "default": ""
          }
        }
      },
      "Person": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PersonList": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Person"
            }
          }
        }
      },
      "JobStatus": {
        "type": "string",
        "enum": [
          "processing",
          "completed",
          "failed"
        ]
      },
      "Job": {
        "type": "object",
        "required": [
          "job_id",
          "status"
        ],
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "result": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          }
        }
      },
      "JobSummary": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Job"
          },
          {
            "type": "object",
            "properties": {
              "tool": {
                "type": "string",
                "enum": [
                  "photoshoots",
                  "video",
                  "models"
                ]
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "JobList": {
        "type": "object",
        "properties": {
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobSummary"
            }
          }
        }
      },
      "JobAccepted": {
        "type": "object",
        "required": [
          "job_id"
        ],
        "properties": {
          "job_id": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "Request body or parameters failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "invalid_request",
                "message": "Invalid request body",
                "hint": "Fix the request body or parameters described in `message` and retry. See the schema at https://hungryshot.com/openapi.json.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, revoked, or expired API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "unauthorized",
                "message": "Invalid or missing API key",
                "hint": "Send `Authorization: Bearer psl_live_<key>` with an active API key created in Studio > Settings > API Keys.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The account owning this key is not on the Expert Pro plan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "forbidden",
                "message": "API access requires the Expert Pro plan",
                "hint": "The API requires the Expert Pro plan. Upgrade the account, or use a key that belongs to an Expert Pro account.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Not enough credits remaining in the shared credit pool.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "insufficient_credits",
                "message": "Insufficient credits",
                "hint": "Top up or upgrade the plan in Studio > Billing, then retry. Check the balance with GET /api/v1/credits.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist for the authenticated caller.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "not_found",
                "message": "Resource not found",
                "hint": "Check the id or path. For async jobs, use the `job_id` returned by the original POST.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-key rate limit exceeded. Wait `Retry-After` seconds before retrying.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "rate_limited",
                "message": "Rate limit exceeded",
                "hint": "Wait for the number of seconds in the `Retry-After` header, then retry. Respect the `RateLimit-*` headers to self-throttle.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server-side failure.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "internal_error",
                "message": "Internal server error",
                "hint": "Retry with exponential backoff. If it persists, contact contact@hungryshot.com.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "UpstreamError": {
        "description": "Generation failed for a reason not exposed to API consumers (internal, DB, or upstream AI provider errors are collapsed to this code). The credit for a failed generation is refunded automatically.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "data": null,
              "error": {
                "code": "upstream_error",
                "message": "Upstream provider error",
                "hint": "An AI provider failed. Retry with exponential backoff; credits for failed generations are refunded automatically.",
                "docs_url": "https://hungryshot.com/api-docs"
              }
            }
          }
        }
      },
      "JobAccepted": {
        "description": "Job accepted and enqueued; generation continues in the background. Poll `GET /jobs/{id}`.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Envelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/JobAccepted"
                    }
                  }
                }
              ]
            },
            "example": {
              "data": {
                "job_id": "b3b9e2b0-3b7a-4e9e-9b0e-2b3b7a4e9e9b"
              },
              "error": null
            }
          }
        }
      }
    }
  }
}