{
  "openapi": "3.0.3",
  "info": {
    "title": "CreatoRain API",
    "version": "1.0.0",
    "description": "A client's own server or AI agent, holding a key for ONE workspace, uses this API to run\nrestaurant video production. Every response carries a `Request-Id` header; every error has\nthe envelope `{error:{type, code, message, field?, doc_url, request_id}}`.\n\nRate limit: 600 requests a minute per workspace, across every server that answers. Above it,\n`429 rate_limited` with `Retry-After`. Twenty failed authentications (missing or unknown key)\nfrom one address within a minute block that address for 15 minutes.\n"
  },
  "servers": [
    {
      "url": "https://api.creatorain.com/v1",
      "description": "Live (crp_live_ keys)"
    },
    {
      "url": "https://sandbox.api.creatorain.com/v1",
      "description": "Sandbox (crp_test_ keys)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "The workspace this key acts for."
    },
    {
      "name": "Onboarding",
      "description": "How an AI agent with no key gets one (RFC 8628 style). The agent opens a session and shows its\nhuman the `user_code` and `url`; the workspace owner signs in to the app, enters the code and\napproves, choosing the mode and scopes themselves; the agent polls and collects the key once.\n"
    },
    {
      "name": "Locations",
      "description": "Add stores to the CreatoRain Network, one or many, or ask first what a call would write and cost (dry_run). Read them back with their network state and this month's pace."
    },
    {
      "name": "Visits",
      "description": "The creator visits booked at this account's stores."
    },
    {
      "name": "Videos",
      "description": "The videos creators posted from this account's stores, with their public numbers."
    },
    {
      "name": "Billing",
      "description": "What this month will cost, as a preview. Nothing here charges."
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "The account this key acts for",
        "description": "Scope `read`. `blockers` is empty and `next_action` is null until the issues that own each\naccount state ship. `counting_locations` counts the live stores of this workspace only.\n",
        "responses": {
          "200": {
            "description": "The account.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/onboarding_sessions": {
      "post": {
        "tags": [
          "Onboarding"
        ],
        "operationId": "createOnboardingSession",
        "summary": "Open an onboarding session (no key)",
        "description": "No API key: this is how an agent without one starts. Show the human `user_code` and `url`.\n`session_secret` is returned ONCE and is the only way to poll; store it like a key. The\nrequested scopes and mode are a request only: the approving human decides. 5 sessions an hour\nper address, and a global per-minute ceiling, then `429 rate_limited` with `Retry-After`.\n",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_name": {
                    "type": "string",
                    "maxLength": 100,
                    "nullable": true,
                    "description": "Shown to the human as an unverified name."
                  },
                  "requested_scopes": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string",
                      "enum": [
                        "read",
                        "samples:read",
                        "locations:write",
                        "balance:write",
                        "creators:read",
                        "webhooks:write"
                      ]
                    }
                  },
                  "requested_livemode": {
                    "type": "boolean",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The session. Poll it every `interval` seconds until it is approved, denied or expired.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSessionCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/onboarding_sessions/{id}": {
      "get": {
        "tags": [
          "Onboarding"
        ],
        "operationId": "getOnboardingSession",
        "summary": "Poll an onboarding session (session secret, no key)",
        "description": "`Authorization: Bearer <session_secret>`. The first poll after the human approves carries\n`key.secret`; no later poll does (`key_delivered: true`, `next_action: create_new_session`\nif it was lost). Polling sooner than `interval` is `429 slow_down`: the interval grows by 5\nseconds, up to 60, and `Retry-After` carries it. A wrong secret and an unknown id are the same\n`404`; failures count toward the address block like bad keys.\n",
        "security": [
          {
            "sessionSecret": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "obs_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The session.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSession"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/locations": {
      "get": {
        "tags": [
          "Locations"
        ],
        "operationId": "listLocations",
        "summary": "This account's locations, oldest first",
        "description": "Scope `read`. Every store of this account on the CreatoRain Network, each with the state the\napp's Network page shows (`state`), its plan and videos a month, and whether it is held\n(`held`, the API hold until CreatoRain releases it) or accepting creators (`accepting`). The\nsame `loc_` id `POST /locations` returned. `deposit` is the quote at create; what Checkout\nwould ask today is `GET /bill/preview`'s `deposit`. `account` is as on the create;\n`network` says whether the network takes bookings at all.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of locations.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ReadUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "operationId": "createLocation",
        "summary": "Add one location to the CreatoRain Network",
        "description": "Scope `locations:write`. The store is created ON HOLD (`state: on_hold`): nothing recruits or\nbooks it until CreatoRain releases it. The plan is the account's: on the paid plan\n`videos_per_month` is 2 to 10 at the locked price per video, and `deposit` quotes the half of\nmonth one (paid later at Checkout, never charged here); on the free plan it is exactly the free\nplan's videos for its term, with no deposit. There is no store maximum. Creator selection is\nCreatoRain's: a creator field (`min_followers`, `rules`, `approval_mode`, ...) is\n`400 customization_not_supported`. The same place again answers `200` with `outcome: repeat`\nand the STORED plan (a create never updates one); a place another team of this account holds\nis `409 location_already_in_network`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Idempotency-Key-Required"
          },
          {
            "$ref": "#/components/parameters/dry_run"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A repeat of a live store (nothing written), or a dry run.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "201": {
            "description": "Created, or an ended store brought back (`outcome: revive`); on hold either way.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/LocationConflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/locations/batch": {
      "post": {
        "tags": [
          "Locations"
        ],
        "operationId": "createLocationsBatch",
        "summary": "Add 1 to 50 locations at once, all or nothing",
        "description": "Each location as in `POST /locations`. All or nothing: any refused item is\n`400 batch_invalid` with every refused item in `error.items` by index, and nothing is written;\na write that fails part way writes nothing either. `201` when any store was created or brought\nback, `200` when every one was a repeat (or a dry run).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Idempotency-Key-Required"
          },
          {
            "$ref": "#/components/parameters/dry_run"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationBatchCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every location was a repeat, or a dry run.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationList"
                }
              }
            }
          },
          "201": {
            "description": "At least one location was created or brought back.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/LocationConflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/locations/{id}": {
      "get": {
        "tags": [
          "Locations"
        ],
        "operationId": "getLocation",
        "summary": "One location, with this month's pace",
        "description": "Scope `read`. The location as in the list, plus `month`: this store-local month's planned\nvideos, the visits booked and done, and the videos posted (stated at the store, the ones the\nbill counts). `month` is null when it cannot be counted. A malformed id, an unknown one and\nanother account's location are the same `404 not_found`. No query parameters.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^loc_[0-9a-f]{32}$",
              "example": "loc_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The location.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ReadUnavailable"
          }
        }
      }
    },
    "/visits": {
      "get": {
        "tags": [
          "Visits"
        ],
        "operationId": "listVisits",
        "summary": "The creator visits at this account's locations, newest update first",
        "description": "Scope `read`. CreatoRain Network visits only: a store's bookings on another campaign of\nits own are not listed, and neither are bookings not yet confirmed or declined.\n`?location=` of another account is `404 not_found` (field `location`). Visits page by their\nlast update, so dedupe on `id` across a walk.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/LocationFilter"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "booked",
                "completed",
                "no_show",
                "cancelled"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "The first visit day, YYYY-MM-DD, in the store's own calendar.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "The last visit day, YYYY-MM-DD, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of visits.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VisitPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ReadUnavailable"
          }
        }
      }
    },
    "/videos": {
      "get": {
        "tags": [
          "Videos"
        ],
        "operationId": "listVideos",
        "summary": "The videos posted from this account's locations, newest first",
        "description": "Scope `read`. Live videos only. A video is this account's when it is stated at one of its\nlocations, or, stated at none, linked to a visit at one, or its creator was booked at one.\n`counted_on_bill` is true only for a video stated at its location: those are what the\nmonth's pace and the bill count, so the list can hold videos not counted on the bill.\n`stats` is null until the public post was first read. Creators appear by public handle only.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/LocationFilter"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of videos.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ReadUnavailable"
          }
        }
      }
    },
    "/bill/preview": {
      "get": {
        "tags": [
          "Billing"
        ],
        "operationId": "getBillPreview",
        "summary": "What a month costs, as a preview",
        "description": "Scope `read`. Read only: nothing is charged (`charged: false`). The month's bill as the app\nshows it, one line per location counted on each store's own clock, in cents; a comped\naccount reads 0 throughout. `total_cents` is null when a line has no locked price yet.\n`deposit` is the half of month one Checkout would ask today, for the stores that joined in\nthe app and still await payment (stores added through this API are not in it). Over 2000\nlocations the answer is `503 service_unavailable` rather than a short bill.\n",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "YYYY-MM. Defaults to the current UTC month.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "example": "2026-11"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The preview.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillPreview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ReadUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer crp_live_... or crp_rk_live_... (live), crp_test_... or crp_rk_test_... (sandbox)."
      },
      "sessionSecret": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer crp_obs_... (the session_secret from POST /onboarding_sessions). Polls one session only."
      }
    },
    "headers": {
      "Request-Id": {
        "description": "This request's id. Equal to error.request_id; quote it to support.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "RateLimit-Limit": {
        "description": "Requests allowed in the current minute for this workspace (600).",
        "schema": {
          "type": "integer",
          "example": 600
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current minute for this workspace.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the current window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (at least 1).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Cache-Control": {
        "description": "no-store. The answer may carry a secret.",
        "schema": {
          "type": "string",
          "enum": [
            "no-store"
          ]
        }
      },
      "Idempotent-Replayed": {
        "description": "true when this answer is the stored response to an earlier request with the same Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "parameters": {
      "Idempotency-Key": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Up to 255 printable ASCII characters, no spaces. Accepted on every POST, PATCH and DELETE;\nrequired on money and location creates. A retry with the same key and body gets the stored\nresponse (`Idempotent-Replayed: true`) for 24 hours; the same key with a different body is\n`409 idempotency_conflict`; while the first request is still running a duplicate is\n`409 idempotency_in_progress`. A 400 or 5xx answer is not stored, so a corrected retry may\nreuse the key.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7E]+$"
        }
      },
      "Idempotency-Key-Required": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "As `Idempotency-Key`, and required here (a location create). Without it the answer is\n`400 idempotency_key_required`. A dry run needs one too: it writes no store row; the\nIdempotency-Key is still stored.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7E]+$"
        }
      },
      "dry_run": {
        "name": "dry_run",
        "in": "query",
        "required": false,
        "description": "`true` validates and quotes: the same refusals as a live call, and writes no store row; the Idempotency-Key is still stored. Ids are null.",
        "schema": {
          "type": "boolean",
          "default": false
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "The `next_cursor` of the previous page, unchanged. Omit it for the first page. Anything else is `400 parameter_invalid` (field `cursor`).",
        "schema": {
          "type": "string",
          "maxLength": 512
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Items a page, 1 to 200.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "LocationFilter": {
        "name": "location",
        "in": "query",
        "required": false,
        "description": "Only this location's items. Another account's location is `404 not_found` (field `location`).",
        "schema": {
          "type": "string",
          "pattern": "^loc_[0-9a-f]{32}$"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "parameter_invalid (with `field`) or invalid_json.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "invalid_key, test_key_on_live or live_key_on_sandbox.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "scope_missing or forbidden_ip.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "not_found.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "idempotency_conflict or idempotency_in_progress.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "rate_limited (600 a minute per workspace, an onboarding create limit, or an address blocked after failed authentication), or slow_down (an onboarding session polled too fast).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "LocationConflict": {
        "description": "location_already_in_network (another team of this account holds the place, or the same place under another name), idempotency_conflict or idempotency_in_progress.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "package_not_available (a plan other than the account's, or a free-plan account bringing back a store that already ran its free term).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "service_unavailable. A concurrent request wrote one of these locations; retry after `Retry-After` with the same key (it was not stored).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ReadUnavailable": {
        "description": "service_unavailable. The read is unavailable right now (for the bill preview, an account over 2000 locations).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "internal_error (500) or service_unavailable (503). The message never carries a cause.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/Request-Id"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "OnboardingSessionCreated": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "url",
          "user_code",
          "session_secret",
          "expires_at",
          "interval"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "obs_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "object": {
            "type": "string",
            "enum": [
              "onboarding_session"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://app.creatorain.com/settings/api/connect?code=BCDF-GHJK"
          },
          "user_code": {
            "type": "string",
            "example": "BCDF-GHJK",
            "description": "Eight letters; the human types it in the app."
          },
          "session_secret": {
            "type": "string",
            "example": "crp_obs_...",
            "description": "Shown once. The only way to poll this session."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "15 minutes after creation."
          },
          "interval": {
            "type": "integer",
            "example": 5,
            "description": "Seconds between polls."
          }
        }
      },
      "OnboardingSession": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "expires_at",
          "interval",
          "key_delivered",
          "key",
          "next_action",
          "blockers"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "enum": [
              "onboarding_session"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "expired"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "interval": {
            "type": "integer"
          },
          "key_delivered": {
            "type": "boolean"
          },
          "key": {
            "type": "object",
            "nullable": true,
            "required": [
              "id",
              "prefix",
              "last4",
              "livemode",
              "scopes"
            ],
            "properties": {
              "id": {
                "type": "string",
                "example": "key_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
              },
              "secret": {
                "type": "string",
                "description": "On the first poll after approval only."
              },
              "prefix": {
                "type": "string",
                "example": "crp_test_AbCd"
              },
              "last4": {
                "type": "string",
                "nullable": true
              },
              "livemode": {
                "type": "boolean"
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "next_action": {
            "type": "object",
            "nullable": true,
            "description": "{type: human_approval, url} while pending; {type: create_new_session} once expired or collected.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "human_approval",
                  "create_new_session"
                ]
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "blockers": {
            "type": "array",
            "description": "Why the approval cannot produce a key yet (for example live_key_requires_top_up, key_limit_reached, owner_changed).",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "id",
          "name",
          "livemode",
          "status",
          "counting_locations",
          "min_locations",
          "max_locations",
          "blockers",
          "next_action",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "acct_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "name": {
            "type": "string"
          },
          "livemode": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_payment",
              "active",
              "past_due",
              "paused",
              "stopped"
            ]
          },
          "counting_locations": {
            "type": "integer",
            "minimum": 0
          },
          "min_locations": {
            "type": "integer",
            "example": 3
          },
          "max_locations": {
            "type": "integer",
            "example": 50
          },
          "blockers": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "next_action": {
            "type": "object",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LocationCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "address",
          "videos_per_month"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "example": "Sexy Crab Duluth"
          },
          "address": {
            "description": "One line, or the parts.",
            "oneOf": [
              {
                "type": "string",
                "minLength": 5,
                "maxLength": 400,
                "example": "2416 Pleasant Hill Rd, Duluth, GA 30096"
              },
              {
                "type": "object",
                "required": [
                  "line1",
                  "city",
                  "state"
                ],
                "properties": {
                  "line1": {
                    "type": "string",
                    "maxLength": 300
                  },
                  "city": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "state": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "postal_code": {
                    "type": "string",
                    "maxLength": 40,
                    "nullable": true
                  },
                  "country": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true
                  }
                }
              }
            ]
          },
          "google_place_id": {
            "type": "string",
            "maxLength": 300,
            "nullable": true
          },
          "plan": {
            "type": "string",
            "enum": [
              "paid",
              "free"
            ],
            "description": "Absent means the account's plan; another plan is 422 package_not_available."
          },
          "videos_per_month": {
            "type": "integer",
            "example": 4,
            "description": "Paid: 2 to 10. Free: exactly the free plan's videos. Otherwise 400 videos_per_month_out_of_range."
          },
          "accepted_terms": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9.-]{2,59}$",
            "nullable": true,
            "example": "network-2026-10",
            "description": "The network terms version accepted",
            "the meal waiver included. Required for a new or returning store (400 terms_not_accepted).": null
          },
          "opening_hours": {
            "type": "object",
            "description": "The store's week",
            "as the partner API takes it. Wins over the lookup.": null
          },
          "signature_dishes": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "maxLength": 300
                },
                {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "maxLength": 300
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "LocationBatchCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "locations"
        ],
        "properties": {
          "locations": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/LocationCreate"
            }
          }
        }
      },
      "LocationAccount": {
        "type": "object",
        "required": [
          "counting_locations",
          "min_locations",
          "blockers"
        ],
        "properties": {
          "counting_locations": {
            "type": "integer",
            "minimum": 0,
            "description": "Live stores of this account after the call (a dry run - as it would be)."
          },
          "min_locations": {
            "type": "integer",
            "example": 3
          },
          "blockers": {
            "type": "array",
            "description": "below_minimum while counting_locations is under min_locations.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "LocationItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "outcome",
          "livemode",
          "name",
          "address",
          "plan",
          "videos_per_month",
          "term_months",
          "price_per_video_cents",
          "currency",
          "deposit",
          "state",
          "dry_run"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "location"
            ]
          },
          "id": {
            "type": "string",
            "nullable": true,
            "example": "loc_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e",
            "description": "Null on a dry run."
          },
          "outcome": {
            "type": "string",
            "enum": [
              "create",
              "repeat",
              "revive"
            ]
          },
          "livemode": {
            "type": "boolean"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "plan": {
            "type": "string",
            "enum": [
              "paid",
              "free"
            ]
          },
          "videos_per_month": {
            "type": "integer",
            "nullable": true
          },
          "term_months": {
            "type": "integer",
            "nullable": true,
            "description": "Free: the free term. Paid: null, until stopped."
          },
          "price_per_video_cents": {
            "type": "integer",
            "nullable": true,
            "example": 30000
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "deposit": {
            "type": "object",
            "required": [
              "cents",
              "charged"
            ],
            "properties": {
              "cents": {
                "type": "integer",
                "nullable": true,
                "description": "The half of month one: videos_per_month x price_per_video_cents / 2. Free: 0."
              },
              "charged": {
                "type": "boolean",
                "enum": [
                  false
                ],
                "description": "Never charged by this call."
              }
            }
          },
          "state": {
            "type": "string",
            "nullable": true,
            "example": "on_hold",
            "description": "The network state; null on a dry run."
          },
          "dry_run": {
            "type": "boolean"
          }
        }
      },
      "Location": {
        "allOf": [
          {
            "$ref": "#/components/schemas/LocationItem"
          },
          {
            "type": "object",
            "required": [
              "account"
            ],
            "properties": {
              "account": {
                "$ref": "#/components/schemas/LocationAccount"
              }
            }
          }
        ]
      },
      "LocationList": {
        "type": "object",
        "required": [
          "object",
          "dry_run",
          "locations",
          "deposit_total_cents",
          "account"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "dry_run": {
            "type": "boolean"
          },
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LocationItem"
            }
          },
          "deposit_total_cents": {
            "type": "integer"
          },
          "account": {
            "$ref": "#/components/schemas/LocationAccount"
          }
        }
      },
      "LocationRead": {
        "type": "object",
        "description": "A location as the reads answer it. The fields it shares with LocationItem carry the same names and meanings.",
        "required": [
          "object",
          "id",
          "livemode",
          "name",
          "address",
          "plan",
          "videos_per_month",
          "term_months",
          "price_per_video_cents",
          "currency",
          "deposit",
          "state",
          "status",
          "held",
          "accepting",
          "term_ends_on",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "location"
            ]
          },
          "id": {
            "type": "string",
            "example": "loc_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "livemode": {
            "type": "boolean"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "plan": {
            "type": "string",
            "enum": [
              "paid",
              "free"
            ]
          },
          "videos_per_month": {
            "type": "integer",
            "nullable": true
          },
          "term_months": {
            "type": "integer",
            "nullable": true
          },
          "price_per_video_cents": {
            "type": "integer",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "deposit": {
            "type": "object",
            "required": [
              "cents",
              "charged"
            ],
            "properties": {
              "cents": {
                "type": "integer",
                "nullable": true,
                "description": "The quote at create (half of month one; free 0). Not what is due now."
              },
              "charged": {
                "type": "boolean",
                "enum": [
                  false
                ]
              }
            }
          },
          "state": {
            "type": "string",
            "enum": [
              "payment_failed",
              "ended",
              "paused",
              "needs_hours",
              "on_hold",
              "awaiting_payment",
              "full_this_month",
              "accepting"
            ],
            "description": "The network state the app's Network page shows."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "ended"
            ]
          },
          "held": {
            "type": "boolean",
            "description": "Held by the API hold until CreatoRain releases it to recruiting."
          },
          "accepting": {
            "type": "boolean",
            "description": "True exactly when state is accepting."
          },
          "term_ends_on": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339, UTC."
          }
        }
      },
      "MonthPace": {
        "type": "object",
        "required": [
          "month",
          "planned",
          "allowance",
          "booked",
          "visited",
          "posted",
          "full",
          "full_until"
        ],
        "properties": {
          "month": {
            "type": "string",
            "example": "2026-11",
            "description": "The store-local month."
          },
          "planned": {
            "type": "integer",
            "nullable": true,
            "description": "Videos a month in force this month."
          },
          "allowance": {
            "type": "integer",
            "nullable": true
          },
          "booked": {
            "type": "integer",
            "description": "Visits this month that are booked or done."
          },
          "visited": {
            "type": "integer",
            "description": "Visits this month that happened."
          },
          "posted": {
            "type": "integer",
            "description": "Videos stated at this location that went live this month."
          },
          "full": {
            "type": "boolean",
            "description": "Held back until next month by the pacing."
          },
          "full_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "LocationDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/LocationRead"
          },
          {
            "type": "object",
            "required": [
              "month"
            ],
            "properties": {
              "month": {
                "nullable": true,
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MonthPace"
                  }
                ]
              }
            }
          }
        ]
      },
      "LocationPage": {
        "type": "object",
        "required": [
          "object",
          "data",
          "next_cursor",
          "account",
          "network"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LocationRead"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true
          },
          "account": {
            "$ref": "#/components/schemas/LocationAccount"
          },
          "network": {
            "type": "object",
            "required": [
              "accepting",
              "join_enabled",
              "comped",
              "renew_to_free"
            ],
            "properties": {
              "accepting": {
                "type": "boolean",
                "description": "The network takes bookings now."
              },
              "join_enabled": {
                "type": "boolean"
              },
              "comped": {
                "type": "boolean",
                "description": "The account is on the free plan."
              },
              "renew_to_free": {
                "type": "boolean",
                "nullable": true
              }
            }
          }
        }
      },
      "Visit": {
        "type": "object",
        "description": "Times are RFC 3339 on the STORE's offset (the restaurant's wall clock), unlike a location's created_at.",
        "required": [
          "object",
          "id",
          "livemode",
          "location_id",
          "status",
          "arrive_at",
          "timezone",
          "party_size",
          "dish",
          "creator",
          "acknowledged_at",
          "acknowledged_kind",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "visit"
            ]
          },
          "id": {
            "type": "string",
            "example": "vis_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "livemode": {
            "type": "boolean"
          },
          "location_id": {
            "type": "string",
            "example": "loc_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "status": {
            "type": "string",
            "enum": [
              "booked",
              "completed",
              "no_show",
              "cancelled"
            ]
          },
          "arrive_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "party_size": {
            "type": "integer"
          },
          "dish": {
            "type": "string",
            "nullable": true
          },
          "creator": {
            "type": "object",
            "required": [
              "handle",
              "followers",
              "platforms"
            ],
            "properties": {
              "handle": {
                "type": "string",
                "nullable": true,
                "description": "The creator's public handle."
              },
              "followers": {
                "type": "integer",
                "nullable": true
              },
              "platforms": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "acknowledged_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When someone at the store first confirmed seeing it."
          },
          "acknowledged_kind": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "VisitPage": {
        "type": "object",
        "required": [
          "object",
          "data",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Visit"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "VideoStats": {
        "type": "object",
        "description": "The public numbers, each its newest reading; null when never read, never 0.",
        "required": [
          "views",
          "likes",
          "comments",
          "measured_at"
        ],
        "properties": {
          "views": {
            "type": "integer",
            "nullable": true
          },
          "likes": {
            "type": "integer",
            "nullable": true
          },
          "comments": {
            "type": "integer",
            "nullable": true
          },
          "measured_at": {
            "type": "object",
            "required": [
              "views",
              "likes",
              "comments"
            ],
            "properties": {
              "views": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "likes": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "comments": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          }
        }
      },
      "Video": {
        "type": "object",
        "required": [
          "object",
          "id",
          "livemode",
          "location_id",
          "visit_id",
          "platform",
          "url",
          "published_at",
          "timezone",
          "status",
          "counted_on_bill",
          "creator",
          "is_collab",
          "stats"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "video"
            ]
          },
          "id": {
            "type": "string",
            "example": "vid_3f2a9c1e5b7d4a6f8e0c2b4d6f8a0c2e"
          },
          "livemode": {
            "type": "boolean"
          },
          "location_id": {
            "type": "string"
          },
          "visit_id": {
            "type": "string",
            "nullable": true,
            "description": "The visit this video is linked to, at the same location; else null."
          },
          "platform": {
            "type": "string",
            "nullable": true,
            "example": "tiktok"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "On the store's offset."
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "live"
            ]
          },
          "counted_on_bill": {
            "type": "boolean",
            "description": "Stated at its location, so counted by the month's pace and the bill. A linked or booked-creator video is listed with false."
          },
          "creator": {
            "type": "object",
            "required": [
              "handle"
            ],
            "properties": {
              "handle": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "is_collab": {
            "type": "boolean",
            "description": "The store's account is a co-author of the public post."
          },
          "stats": {
            "nullable": true,
            "description": "Null until the public post was first read.",
            "allOf": [
              {
                "$ref": "#/components/schemas/VideoStats"
              }
            ]
          }
        }
      },
      "VideoPage": {
        "type": "object",
        "required": [
          "object",
          "data",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Video"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "BillPreviewLine": {
        "type": "object",
        "required": [
          "location_id",
          "name",
          "status",
          "plan_videos_per_month",
          "visits_completed",
          "no_shows",
          "videos_published",
          "makeup_videos",
          "billed_videos",
          "amount_cents"
        ],
        "properties": {
          "location_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "terminated"
            ]
          },
          "plan_videos_per_month": {
            "type": "integer",
            "nullable": true
          },
          "visits_completed": {
            "type": "integer"
          },
          "no_shows": {
            "type": "integer"
          },
          "videos_published": {
            "type": "integer"
          },
          "makeup_videos": {
            "type": "integer"
          },
          "billed_videos": {
            "type": "integer",
            "description": "Excludes free-plan videos (TASK-2916): they are counted, never billed."
          },
          "amount_cents": {
            "type": "integer",
            "nullable": true,
            "description": "Null when the location has no locked price; 0 when comped."
          }
        }
      },
      "BillPreview": {
        "type": "object",
        "description": "A preview. Never charged. Over 2000 locations the route answers 503 instead of a short bill.",
        "required": [
          "object",
          "livemode",
          "month",
          "currency",
          "preview",
          "charged",
          "comped",
          "lines",
          "total_cents",
          "deposit",
          "generated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "bill_preview"
            ]
          },
          "livemode": {
            "type": "boolean"
          },
          "month": {
            "type": "string",
            "example": "2026-11"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "preview": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "charged": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "comped": {
            "type": "boolean"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillPreviewLine"
            }
          },
          "total_cents": {
            "type": "integer",
            "nullable": true,
            "description": "Null when any line is null."
          },
          "deposit": {
            "type": "object",
            "required": [
              "cents",
              "location_ids",
              "charged"
            ],
            "properties": {
              "cents": {
                "type": "integer",
                "description": "The half of month one Checkout would ask today."
              },
              "location_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "charged": {
                "type": "boolean",
                "enum": [
                  false
                ]
              }
            }
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message",
              "doc_url",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "idempotency_error",
                  "rate_limit_error",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "enum": [
                  "parameter_invalid",
                  "invalid_json",
                  "idempotency_key_invalid",
                  "idempotency_key_required",
                  "customization_not_supported",
                  "videos_per_month_out_of_range",
                  "batch_invalid",
                  "terms_not_accepted",
                  "invalid_key",
                  "test_key_on_live",
                  "live_key_on_sandbox",
                  "insufficient_balance",
                  "scope_missing",
                  "forbidden_ip",
                  "forbidden",
                  "live_key_requires_top_up",
                  "limit_raise_requires_dashboard",
                  "not_found",
                  "idempotency_conflict",
                  "idempotency_in_progress",
                  "conflict",
                  "location_already_in_network",
                  "external_id_conflict",
                  "dispute_open",
                  "request_too_large",
                  "address_not_verified",
                  "not_food_business",
                  "metro_not_served",
                  "package_not_available",
                  "account_location_limit",
                  "commit_limit_exceeded",
                  "top_up_exceeds_feasible",
                  "no_saved_card",
                  "rate_limited",
                  "quota_exceeded",
                  "slow_down",
                  "internal_error",
                  "service_unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string",
                "description": "The request field at fault",
                "when one is.": null
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "example": "https://www.creatorain.com/api/errors#scope_missing"
              },
              "request_id": {
                "type": "string",
                "format": "uuid"
              },
              "items": {
                "type": "array",
                "description": "batch_invalid only. Every refused location, by its index in the request.",
                "items": {
                  "type": "object",
                  "required": [
                    "index",
                    "code",
                    "message"
                  ],
                  "properties": {
                    "index": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "code": {
                      "type": "string",
                      "description": "One of the error codes above."
                    },
                    "field": {
                      "type": "string",
                      "example": "locations.1.videos_per_month"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}