---
openapi: 3.0.3
info:
  title: Hotdog API
  version: '1.0'
  description: |
    Send a picture, find out whether it shows a hot dog.

    **Get started in three steps, right from this page:**
    1. Open *Get a key* → `POST /v1/keys`, click *Try it out*, type your name, *Execute*.
       **Copy the `api_key` from the answer: it is shown only once.**
    2. Click *Authorize* (top right), paste the key, *Authorize*.
    3. Try any endpoint. `GET /v1/me` is a good first call: it checks that your key works.

    Your key is free (100 classifications a day). To practise without using your quota, the sandbox keys
    always give the same verdict: `hd_test_hotdog_000000000000000000000000000000` (always a hot dog) and
    `hd_test_nothotdog_0000000000000000000000000000` (never one). The shared demo key
    `hd_demo_0000000000000000000000000000000000000000` allows only 20 requests a day for everybody.

    Images are analysed in memory and never stored. Only the verdict is kept.

    Every error has the same shape, `{"error": {"code": "...", "message": "..."}}`, and `code` is stable.
servers:
- url: "/"
security:
- ApiKeyAuth: []
- BearerAuth: []
tags:
- name: Get a key
  description: Sign up for a free API key, no account needed
- name: Discovery
  description: Check the API and your key, and find example images
- name: Classification
  description: Is it a hot dog? Send an image or a URL
- name: History
  description: The verdicts you obtained, kept without the image
- name: Hot dogs
  description: Your own collection of hot dogs
- name: Webhooks
  description: Be told when a classification is completed
paths:
  "/v1/keys":
    post:
      operationId: createApiKey
      tags:
      - Get a key
      summary: Get a free API key
      description: |
        No account, e-mail or password: the key is your identity. It is shown **only once**, so store it.
        If you lose it, create another one. Free plan: 100 classifications a day. Reading your data (history,
        hot dogs, webhooks) is not counted. Sign-ups are limited per IP address (30 an hour).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  maxLength: 60
                  example: Jian Yang
                  description: Who or what the key is for (your name, the app you
                    build...)
      responses:
        '201':
          description: Your key. Copy `api_key` now.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/NewApiKey"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
        '429':
          "$ref": "#/components/responses/RateLimited"
  "/v1/ping":
    get:
      operationId: ping
      tags:
      - Discovery
      summary: Availability probe
      description: Needs no key and touches neither the database nor the model.
      security: []
      responses:
        '200':
          description: The API is up
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: This app is not Jian Yang related
        '429':
          "$ref": "#/components/responses/RateLimited"
  "/v1/me":
    get:
      operationId: getMe
      tags:
      - Discovery
      summary: Your plan and remaining quota
      description: Also a quick way to check that a key works. Does not use any quota.
      responses:
        '200':
          description: The key's plan and its daily quota
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Me"
        '401':
          "$ref": "#/components/responses/Unauthorized"
    delete:
      operationId: revokeApiKey
      tags:
      - Get a key
      summary: Revoke your own key
      description: |
        Deletes the key used for the request and everything attached to it (history, hot dogs, webhooks).
        It stops working at once. The shared demo and sandbox keys cannot be revoked.
      responses:
        '204':
          description: Revoked
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '403':
          description: The shared demo and sandbox keys cannot be revoked (`key_not_revocable`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/samples":
    get:
      operationId: listSamples
      tags:
      - Discovery
      summary: Example images
      description: |
        Pictures to try the API without a file of your own, with the expected verdict.
        Pass a `url` to `POST /v1/classifications/from_url` (the API must be reachable from the internet,
        as private addresses are refused), or download it and upload it.
      security: []
      responses:
        '200':
          description: The samples
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Sample"
        '429':
          "$ref": "#/components/responses/RateLimited"
  "/v1/classifications":
    post:
      operationId: classifyUpload
      tags:
      - Classification
      summary: Classify an uploaded image
      description: |
        Accepted formats: JPEG, PNG, GIF (first frame), WebP, TIFF, HEIC and AVIF. The format is detected from
        the content, never from the file name or `Content-Type`. Limits: 10 MB and 50 million pixels.
        Counts as one request against the daily quota.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - image
              properties:
                image:
                  type: string
                  format: binary
                  description: The picture
                label:
                  type: string
                  maxLength: 100
                  description: Optional free-form name, kept in your history
      responses:
        '201':
          "$ref": "#/components/responses/ClassificationCreated"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '413':
          "$ref": "#/components/responses/TooLarge"
        '415':
          "$ref": "#/components/responses/UnsupportedFormat"
        '422':
          "$ref": "#/components/responses/Unprocessable"
        '429':
          "$ref": "#/components/responses/QuotaExceeded"
        '503':
          "$ref": "#/components/responses/ModelUnavailable"
    get:
      operationId: listClassifications
      tags:
      - History
      summary: Your classification history
      description: Newest first, cursor-paginated. Does not use any quota.
      parameters:
      - "$ref": "#/components/parameters/Limit"
      - "$ref": "#/components/parameters/After"
      - "$ref": "#/components/parameters/CreatedAfter"
      - name: hotdog
        in: query
        description: Keep only hot dogs (`true`) or only the rest (`false`)
        schema:
          type: boolean
      responses:
        '200':
          description: A page of history
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/Page"
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        "$ref": "#/components/schemas/Classification"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
  "/v1/classifications/from_url":
    post:
      operationId: classifyUrl
      tags:
      - Classification
      summary: Classify an image found at a URL
      description: |
        The server downloads the image. Only `http` and `https` on ports 80 and 443 are accepted, without
        credentials in the URL. Private, local and link-local addresses are refused, after DNS resolution and
        at every redirect (3 at most). Counts as one request against the daily quota.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  format: uri
                  example: https://upload.wikimedia.org/wikipedia/commons/b/b1/Hot_dog_with_mustard.png
                label:
                  type: string
                  maxLength: 100
      responses:
        '201':
          "$ref": "#/components/responses/ClassificationCreated"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '413':
          "$ref": "#/components/responses/TooLarge"
        '415':
          "$ref": "#/components/responses/UnsupportedFormat"
        '422':
          description: |
            The image is corrupted (`invalid_image`), the URL is invalid (`invalid_url`), points to a private
            address (`url_not_allowed`), or could not be fetched (`url_unreachable`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          "$ref": "#/components/responses/QuotaExceeded"
        '503':
          "$ref": "#/components/responses/ModelUnavailable"
  "/v1/classifications/{id}":
    parameters:
    - "$ref": "#/components/parameters/Id"
    get:
      operationId: getClassification
      tags:
      - History
      summary: One history entry
      responses:
        '200':
          description: The entry
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Classification"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
    patch:
      operationId: updateClassification
      tags:
      - History
      summary: Rename an entry
      description: Only `label` can be changed.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                  maxLength: 100
                  nullable: true
      responses:
        '200':
          description: The updated entry
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Classification"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
    delete:
      operationId: deleteClassification
      tags:
      - History
      summary: Delete an entry
      responses:
        '204':
          description: Deleted
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
  "/v1/hotdogs":
    get:
      operationId: listHotdogs
      tags:
      - Hot dogs
      summary: List your hot dogs
      description: Newest first, cursor-paginated.
      parameters:
      - "$ref": "#/components/parameters/Limit"
      - "$ref": "#/components/parameters/After"
      - "$ref": "#/components/parameters/CreatedAfter"
      - name: style
        in: query
        schema:
          "$ref": "#/components/schemas/HotdogStyle"
      - name: min_rating
        in: query
        description: Keep hot dogs rated at least this (unrated ones are left out)
        schema:
          type: integer
          minimum: 1
          maximum: 5
      - name: q
        in: query
        description: Part of the name
        schema:
          type: string
      - name: spot_id
        in: query
        description: Only the hot dogs sold at this spot
        schema:
          type: integer
      responses:
        '200':
          description: A page of hot dogs
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/Page"
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        "$ref": "#/components/schemas/Hotdog"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
    post:
      operationId: createHotdog
      tags:
      - Hot dogs
      summary: Add a hot dog
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/HotdogInput"
          application/x-www-form-urlencoded:
            schema:
              "$ref": "#/components/schemas/HotdogInput"
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Hotdog"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
  "/v1/hotdogs/{id}":
    parameters:
    - "$ref": "#/components/parameters/Id"
    get:
      operationId: getHotdog
      tags:
      - Hot dogs
      summary: One hot dog
      responses:
        '200':
          description: The hot dog
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Hotdog"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
    patch:
      operationId: updateHotdog
      tags:
      - Hot dogs
      summary: Update a hot dog
      description: Only the fields you send are changed.
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/HotdogInput"
      responses:
        '200':
          description: The updated hot dog
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Hotdog"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
    delete:
      operationId: deleteHotdog
      tags:
      - Hot dogs
      summary: Delete a hot dog
      responses:
        '204':
          description: Deleted
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
  "/v1/spots":
    get:
      operationId: listSpots
      tags:
      - Hot dogs
      summary: List your spots
      description: |
        The places that sell hot dogs. This is the list to use for a **dynamic dropdown** (a "dynamic select"
        in Make and Zapier, options loaded from the API). Newest first, cursor-paginated.
      parameters:
      - "$ref": "#/components/parameters/Limit"
      - "$ref": "#/components/parameters/After"
      - "$ref": "#/components/parameters/CreatedAfter"
      - name: city
        in: query
        description: Only the spots of this city (not case sensitive)
        schema:
          type: string
      - name: q
        in: query
        description: Part of the name
        schema:
          type: string
      responses:
        '200':
          description: A page of spots
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/Page"
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        "$ref": "#/components/schemas/Spot"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
    post:
      operationId: createSpot
      tags:
      - Hot dogs
      summary: Add a spot
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SpotInput"
          application/x-www-form-urlencoded:
            schema:
              "$ref": "#/components/schemas/SpotInput"
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Spot"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
  "/v1/spots/{id}":
    parameters:
    - "$ref": "#/components/parameters/Id"
    get:
      operationId: getSpot
      tags:
      - Hot dogs
      summary: One spot
      responses:
        '200':
          description: The spot
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Spot"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
  "/v1/usage":
    get:
      operationId: getUsage
      tags:
      - Discovery
      summary: Your calls, per integration
      description: |
        Counts the calls made with your key under each `X-Integration-Source` value (`zapier`, `make`, `n8n`,
        `postman`...), and how many failed. This is how you measure which platform brings the traffic.
        Calls sent without the header are counted as `unknown`.
      responses:
        '200':
          description: One entry per source, the busiest first
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/SourceUsage"
                  total_requests:
                    type: integer
        '401':
          "$ref": "#/components/responses/Unauthorized"
  "/v1/errors/{status}":
    get:
      operationId: simulateError
      tags:
      - Discovery
      summary: Get an error on purpose
      description: |
        Answers with the error you ask for, in the usual format, so you can test how your integration handles it
        (Make's review requires a scenario that fails on purpose). Nothing is really broken. Available: 400
        (`bad_request`), 401 (`invalid_api_key`), 403 (`forbidden`), 404 (`not_found`), 422 (`validation_failed`),
        429 (`rate_limited`, with `Retry-After: 2`), 500 (`internal_error`) and 503 (`service_unavailable`).
        The call is counted as an error in `GET /v1/usage`.
      parameters:
      - name: status
        in: path
        required: true
        schema:
          type: integer
          enum:
          - 400
          - 401
          - 403
          - 404
          - 422
          - 429
          - 500
          - 503
          example: 429
      responses:
        '400':
          "$ref": "#/components/responses/BadRequest"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '403':
          description: Simulated 403
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          "$ref": "#/components/responses/NotFound"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
        '429':
          "$ref": "#/components/responses/RateLimited"
        '500':
          description: Simulated 500 (`internal_error`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '503':
          description: Simulated 503 (`service_unavailable`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/me/data":
    get:
      operationId: getMyData
      tags:
      - Get a key
      summary: Everything your key has stored, in one call
      description: |
        Your hot dogs, spots, classification history, webhooks and usage figures, each list newest first and
        limited to 100 entries (`next_cursor` is not `null` when there are more: use the list endpoints to go on).
        This is what the home page shows in "My data".

        The answer carries an `ETag`. Send it back as `If-None-Match` to get an empty `304` as long as nothing has
        changed: the cheap way to keep a screen up to date, instead of reading each list again. The usage figures
        are part of it, so an ordinary call made with the key counts as a change; the home page sends
        `X-Integration-Source: interface-sync`, which is not counted.
      responses:
        '200':
          description: Your data
          headers:
            ETag:
              description: Changes whenever something is added
              changed or removed:
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                properties:
                  hotdogs:
                    allOf:
                    - "$ref": "#/components/schemas/Page"
                    - type: object
                      properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/Hotdog"
                  spots:
                    allOf:
                    - "$ref": "#/components/schemas/Page"
                    - type: object
                      properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/Spot"
                  classifications:
                    allOf:
                    - "$ref": "#/components/schemas/Page"
                    - type: object
                      properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/Classification"
                  webhooks:
                    allOf:
                    - "$ref": "#/components/schemas/Page"
                    - type: object
                      properties:
                        data:
                          type: array
                          items:
                            "$ref": "#/components/schemas/Webhook"
                  usage:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/SourceUsage"
                      total_requests:
                        type: integer
        '304':
          description: Nothing has changed since the `ETag` you sent
        '401':
          "$ref": "#/components/responses/Unauthorized"
    delete:
      operationId: resetMyData
      tags:
      - Get a key
      summary: Empty everything and start again
      description: |
        Deletes your hot dogs, spots, history, webhooks and usage figures, and **keeps your key and its quota**.
        Handy to repeat an exercise from scratch. No webhook event is sent. Not available on the shared demo and
        sandbox keys.
      responses:
        '204':
          description: Everything is gone
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '403':
          description: The shared demo and sandbox keys cannot be reset (`key_not_allowed`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/v1/webhooks":
    get:
      operationId: listWebhooks
      tags:
      - Webhooks
      summary: List your webhooks
      parameters:
      - "$ref": "#/components/parameters/Limit"
      - "$ref": "#/components/parameters/After"
      responses:
        '200':
          description: A page of webhooks
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/Page"
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        "$ref": "#/components/schemas/Webhook"
        '401':
          "$ref": "#/components/responses/Unauthorized"
    post:
      operationId: createWebhook
      tags:
      - Webhooks
      summary: Subscribe a URL to classification.completed
      description: |
        Up to 5 per key, with **your own key** (the shared demo and sandbox keys cannot use webhooks). In production the URL must be `https`, on a webhook domain of your platform (Make, Zapier, n8n...); ports other than 80 and 443 are refused. Choose the events with `events`: `classification.completed`, `hotdog.created`, `hotdog.updated`, `hotdog.deleted`.
        **The `secret` is shown only in this response.** Each event is a signed `POST`:
        header `X-Hotdog-Signature: t=<unix time>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with the secret>`.
        Any 2xx answer is a success; otherwise the delivery is retried (5 attempts in all). Redirects are not followed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  format: uri
                  example: https://example.com/hotdog-hook
                events:
                  type: array
                  description: The events to receive. Without it, all of them.
                  items:
                    "$ref": "#/components/schemas/WebhookEvent"
                  example:
                  - hotdog.created
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/WebhookWithSecret"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '403':
          description: The shared demo and sandbox keys cannot use webhooks (`key_not_allowed`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          "$ref": "#/components/responses/ValidationFailed"
  "/v1/webhooks/{id}":
    parameters:
    - "$ref": "#/components/parameters/Id"
    get:
      operationId: getWebhook
      tags:
      - Webhooks
      summary: One webhook
      responses:
        '200':
          description: The webhook (without its secret)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Webhook"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
    delete:
      operationId: deleteWebhook
      tags:
      - Webhooks
      summary: Unsubscribe
      responses:
        '204':
          description: Deleted, with its delivery log
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
  "/v1/webhooks/{id}/test":
    parameters:
    - "$ref": "#/components/parameters/Id"
    post:
      operationId: testWebhook
      tags:
      - Webhooks
      summary: Send a sample event now
      description: Delivers a `webhook.test` event synchronously. Counts as one request
        against the daily quota.
      responses:
        '200':
          description: The result, whether it worked or not (see `status`)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Delivery"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
        '429':
          "$ref": "#/components/responses/QuotaExceeded"
  "/v1/webhooks/{id}/deliveries":
    parameters:
    - "$ref": "#/components/parameters/Id"
    get:
      operationId: listWebhookDeliveries
      tags:
      - Webhooks
      summary: Delivery log of a webhook
      description: Newest first, cursor-paginated.
      parameters:
      - "$ref": "#/components/parameters/Limit"
      - "$ref": "#/components/parameters/After"
      responses:
        '200':
          description: A page of deliveries
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/Page"
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        "$ref": "#/components/schemas/Delivery"
        '401':
          "$ref": "#/components/responses/Unauthorized"
        '404':
          "$ref": "#/components/responses/NotFound"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your API key. Send an `X-Integration-Source` header too (zapier,
        make, n8n, postman...) so your calls are counted per platform (`GET /v1/usage`).
    BearerAuth:
      type: http
      scheme: bearer
      description: 'The same key, as `Authorization: Bearer <key>`. Use whichever
        your platform prefers.'
  parameters:
    Id:
      name: id
      in: path
      required: true
      schema:
        type: integer
    Limit:
      name: limit
      in: query
      description: Page size
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    CreatedAfter:
      name: created_after
      in: query
      description: Keep what was created strictly after this moment (ISO 8601, e.g.
        `2026-10-16T18:00:00Z`). Handy for a polling trigger.
      schema:
        type: string
        format: date-time
    After:
      name: after
      in: query
      description: The `next_cursor` of the previous page
      schema:
        type: string
        example: '41'
  headers:
    RateLimitLimit:
      description: Requests allowed per day for your key
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests left today
      schema:
        type: integer
    RateLimitReset:
      description: Seconds until the quota resets (midnight UTC)
      schema:
        type: integer
  responses:
    ClassificationCreated:
      description: The verdict, also recorded in your history
      headers:
        RateLimit-Limit:
          "$ref": "#/components/headers/RateLimitLimit"
        RateLimit-Remaining:
          "$ref": "#/components/headers/RateLimitRemaining"
        RateLimit-Reset:
          "$ref": "#/components/headers/RateLimitReset"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Classification"
    BadRequest:
      description: A parameter is missing or malformed (`parameter_missing`, `bad_request`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    Unauthorized:
      description: No key (`missing_api_key`) or an unknown key (`invalid_api_key`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    ModelUnavailable:
      description: |
        The server cannot classify right now: the model is not loaded (`model_unavailable`) or libvips is
        missing (`image_processing_unavailable`). Retrying will not help until the operator fixes it.
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    NotFound:
      description: No such resource, also when it belongs to someone else (`not_found`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    TooLarge:
      description: Over 10 MB or 50 million pixels (`image_too_large`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    UnsupportedFormat:
      description: Not a supported image format (`unsupported_image_format`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    Unprocessable:
      description: Truncated or corrupted image (`invalid_image`)
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    ValidationFailed:
      description: Invalid fields (`validation_failed`); `error.details` lists them
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    QuotaExceeded:
      description: Daily quota used up (`quota_exceeded`) or too many requests from
        your IP (`rate_limited`)
      headers:
        Retry-After:
          description: Seconds to wait
          schema:
            type: integer
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    RateLimited:
      description: Too many requests from your IP (`rate_limited`)
      headers:
        Retry-After:
          description: Seconds to wait
          schema:
            type: integer
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
              example: invalid_api_key
            message:
              type: string
              example: Invalid API key
            details:
              type: object
              description: Present on `validation_failed`, the problems field by field
              additionalProperties:
                type: array
                items:
                  type: string
              example:
                name:
                - can't be blank
    Page:
      type: object
      properties:
        next_cursor:
          type: string
          nullable: true
          description: Pass it as `after` to get the next page. `null` on the last
            page.
    NewApiKey:
      type: object
      properties:
        api_key:
          type: string
          example: hd_3f9c0a7e5b1d4c8a92e6f0b7d1a5c3e84f2b6a9d1c7e0f35
          description: Shown only once
        name:
          type: string
          example: Jian Yang
        plan:
          type: string
          example: free
        quota:
          "$ref": "#/components/schemas/Quota"
        warning:
          type: string
    Quota:
      type: object
      properties:
        limit:
          type: integer
          example: 100
        used:
          type: integer
          example: 0
        remaining:
          type: integer
          example: 100
        period:
          type: string
          example: day
        resets_at:
          type: string
          format: date-time
    Me:
      type: object
      properties:
        name:
          type: string
          example: Erlich's demo account
        plan:
          type: string
          enum:
          - demo
          - free
          - pro
          - sandbox
        quota:
          "$ref": "#/components/schemas/Quota"
    Classification:
      type: object
      properties:
        id:
          type: integer
          example: 7
        hotdog:
          type: boolean
          description: True when hotdog is the top class or its score reaches 0.5
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
          description: Probability that `hotdog` is right
          example: 0.93
        top_label:
          type: string
          description: The most likely of the 1000 ImageNet classes
          example: hotdog
        label:
          type: string
          nullable: true
          description: Your own free-form name
        source:
          type: string
          enum:
          - upload
          - url
        created_at:
          type: string
          format: date-time
    HotdogStyle:
      type: string
      enum:
      - chicago
      - coney_island
      - corn_dog
      - new_york
      - sonoran
      - seattle
      - german
      - other
    Hotdog:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
          example: Windy City
        style:
          "$ref": "#/components/schemas/HotdogStyle"
        toppings:
          type: array
          items:
            type: string
          example:
          - mustard
          - relish
        rating:
          type: integer
          minimum: 1
          maximum: 5
          nullable: true
        spot_id:
          type: integer
          nullable: true
        spot:
          type: object
          nullable: true
          description: The spot, nested (null when the hot dog has none)
          properties:
            id:
              type: integer
            name:
              type: string
              example: Lou's Hot Dogs
            city:
              type: string
              nullable: true
              example: Chicago
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SpotSummary:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
          example: Lou's Hot Dogs
        city:
          type: string
          nullable: true
          example: Chicago
    Spot:
      allOf:
      - "$ref": "#/components/schemas/SpotSummary"
      - type: object
        properties:
          created_at:
            type: string
            format: date-time
          updated_at:
            type: string
            format: date-time
    SpotInput:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          maxLength: 100
          example: Lou's Hot Dogs
        city:
          type: string
          maxLength: 100
          example: Chicago
    SourceUsage:
      type: object
      properties:
        source:
          type: string
          example: n8n
          description: The X-Integration-Source value (a lowercase word), `unknown`
            when absent, `other` when it is not a short word
        requests:
          type: integer
        errors:
          type: integer
          description: Calls answered with an error
        first_seen_at:
          type: string
          format: date-time
        last_seen_at:
          type: string
          format: date-time
    HotdogInput:
      type: object
      required:
      - name
      properties:
        name:
          type: string
          maxLength: 100
          example: Windy City
        style:
          "$ref": "#/components/schemas/HotdogStyle"
        toppings:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 40
        rating:
          type: integer
          minimum: 1
          maximum: 5
        spot_id:
          type: integer
          description: One of your spots (see GET /v1/spots)
    WebhookEvent:
      type: string
      enum:
      - classification.completed
      - hotdog.created
      - hotdog.updated
      - hotdog.deleted
    Webhook:
      type: object
      properties:
        id:
          type: integer
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            "$ref": "#/components/schemas/WebhookEvent"
        created_at:
          type: string
          format: date-time
    WebhookWithSecret:
      allOf:
      - "$ref": "#/components/schemas/Webhook"
      - type: object
        properties:
          secret:
            type: string
            example: whsec_0e692dbcd4eaee8eb3b9167dc704eac1a22e009fa1b25347
            description: Shown only once
    Delivery:
      type: object
      properties:
        id:
          type: integer
        event:
          type: string
          enum:
          - classification.completed
          - hotdog.created
          - hotdog.updated
          - hotdog.deleted
          - webhook.test
        status:
          type: string
          enum:
          - pending
          - succeeded
          - failed
        attempts:
          type: integer
        response_code:
          type: integer
          nullable: true
        error:
          type: string
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    Sample:
      type: object
      properties:
        name:
          type: string
          example: hotdog-1.jpg
        url:
          type: string
          format: uri
        hotdog:
          type: boolean
          description: The expected verdict
        description:
          type: string
        credit:
          type: string
        license:
          type: string
          example: CC0
