# Dotb Public API > Prepare Vinted listings for import: create pending items and upload their photos, then > import them into Vinted through the Dotb browser extension. Also access Vinted orders for > shipping and accounting, and look up Vinted reference data (catalogs, brands, colors, > sizes, video game ratings) and connected Vinted accounts. Requires a Boutique AI plan. - Base URL: https://dotb.io - Auth: Bearer token (`dotb_pk_...`) in the `Authorization` header. Requires a Boutique AI plan. - OpenAPI spec: /api/public/v1/openapi.json - Interactive docs: /api/public/v1/docs Errors share one shape: `{"error": {"type", "message", "details", "request_id"}}`. Common statuses: 401 (missing/invalid token), 402 (no Boutique AI subscription), 404 (not found or not owned), 422 (validation error). Rate limits apply per API token (not per user) — each token gets its own allowance of 60 requests/minute and 10,000 requests/day. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers; exceeding a limit returns 429 with a `Retry-After` header (seconds). ### Accounts ## GET /api/public/v1/accounts — List Vinted accounts Returns the Vinted accounts connected to the authenticated user. **Response 200:** List of accounts. Fields: `data` (array) — `id` (uuid), `login` (string), `vinted_id` (integer), `country` (string, nullable), `bridge` (object) — state of the browser extension's bridge for the account: `connected` (boolean, true when a publish-capable extension is live on it right now, i.e. a scheduled publish will be accepted), `last_seen_at` (datetime, nullable — when the extension was last seen on the bridge, connected or not; held in memory, so null for an account that has not polled since the last server restart). ### Catalogs ## GET /api/public/v1/catalogs — List catalogs Returns the Vinted catalog tree up to a given depth for the resolved country. Responses are publicly cacheable for 1 hour. **Query parameters:** - `country` (string, optional) — ISO 3166-1 alpha-2 country code (e.g. "FR"), 2 chars. Inferred from account when omitted. - `depth` (integer, optional) — Maximum tree depth to return (0–6, default 2). **Response 200:** List of catalogs. Fields: `data` (array) — `id` (integer), `title` (string), `parent_id` (integer, nullable), `depth` (integer). ## GET /api/public/v1/catalogs/{catalog_id} — Get catalog Returns a single catalog by its Vinted id along with its ancestor breadcrumb. **Query parameters:** - `catalog_id` (integer, required, path) — Vinted catalog id. - `country` (string, optional) — ISO 3166-1 alpha-2 country code. Inferred from account when omitted. **Response 200:** Catalog with ancestors. Fields: `catalog` (object: `id`, `title`, `parent_id` nullable, `depth`), `ancestors` (array of `id`, `title`). ## GET /api/public/v1/catalogs/{catalog_id}/options — Get catalog form options Returns the available conditions, package sizes, size groups, and attributes for a catalog — i.e. everything needed to render a listing form. When `parcel_measurements` is set, the catalog ships by measured parcel (snowboards, skis, power tools…): send the item's `parcel` instead of picking a `package_size_id`. **Query parameters:** - `catalog_id` (integer, required, path) — Vinted catalog id. - `country` (string, optional) — ISO 3166-1 alpha-2 country code. Inferred from account when omitted. - `locale` (string, optional) — BCP 47 locale code for translated titles. Defaults to the country's language. **Response 200:** Catalog options. Fields: `catalog` (object), `ancestors` (array of `id`, `title`), `conditions` (array — `id`, `title`, `explanation`), `package_sizes` (array — `id`, `title`, `description`, `standard` boolean), `size_groups` (array — `id`, `title`, `sizes`: array of `id`/`title`), `attributes` (array — `code`, `title`, `required` boolean, `selection_type` nullable, `selection_limit` nullable, `display_type` nullable, `options`: array of `id`/`title`), `parcel_measurements` (object, nullable — set when the catalog ships by measured parcel: `suggested_weight` number nullable, Vinted's default weight to pre-fill; `weight_unit` string, `"kg"`; `dimension_unit` string, `"cm"`; `limits` object — Vinted's shipping limits, enforced by POST/PATCH /items: `max_side_cm` integer (longest side at most, 100), `max_sides_sum_cm` integer (height + length + width at most, 250), `weight_under_kg` number (weight strictly under, 31)). ## GET /api/public/v1/catalogs/{catalog_id}/brands/{brand_id}/models — List brand models Returns the model picker for a catalog + brand pair — Vinted offers one for phones, tablets, laptops, headphones and some luxury bags. Send the picked row as the item's `model`: `{"name": row.name, "metadata": row.metadata}`. Most brands list flat models (iPhone 15 Pro); a few group them, listing a collection (Neverfull) and then its models (Neverfull MM) — either can be picked. GET /brands/with-models lists the pairs that have one; an empty list means the pair has none. **Query parameters:** - `catalog_id` (integer, required, path) — Vinted catalog id. - `brand_id` (integer, required, path) — Vinted brand id. - `country` (string, optional) — ISO 3166-1 alpha-2 country code. Inferred from account when omitted. **Response 200:** Brand models. Fields: `data` (array — `key` string, opaque row id e.g. `"2233-2235"`; `name` string; `metadata` object, sent as is as the item's `model.metadata` — `collection_id` integer, plus `model_id` integer only on a collection's models; `has_children` boolean — picking it brings fields of its own, see the next endpoint). ## GET /api/public/v1/catalogs/{catalog_id}/brands/{brand_id}/models/attributes — Get model attributes Returns the extra fields a picked model brings with it (e.g. an iPhone's required `battery_health`), shaped like the `attributes` of the catalog options endpoint. Only needed for rows with `has_children: true`; send the answers in the item's `item_attributes`. **Query parameters:** - `catalog_id` (integer, required, path) — Vinted catalog id. - `brand_id` (integer, required, path) — Vinted brand id. - `country` (string, optional) — ISO 3166-1 alpha-2 country code. Inferred from account when omitted. - `collection_id` (integer, required) — The picked row's `metadata.collection_id`. - `model_id` (integer, optional) — The picked row's `metadata.model_id`, when it has one. - `locale` (string, optional) — Language code for translated titles (e.g. "en"). Defaults to the country's language. **Response 200:** Model attributes. Fields: `data` (array — `code`, `title`, `required` boolean, `selection_type` nullable, `selection_limit` nullable, `display_type` nullable, `options`: array of `id`/`title`). ### Brands ## GET /api/public/v1/brands — Search brands Full-text search over Vinted brands. The `q` query parameter is required. **Query parameters:** - `q` (string, required) — Search query (required, non-empty). - `limit` (integer, optional) — Maximum number of results (1–100, default 25). **Response 200:** Brand search results. Fields: `data` (array — `id` integer, `title` string), `has_more` (boolean), `next_cursor` (string, nullable). ## GET /api/public/v1/brands/with-models — List brands with a model picker Returns every brand Vinted offers a model picker for (phones, tablets, laptops, headphones, some luxury bags), each with the catalogs it has one in. For an item whose `catalog_id` is in a brand's `catalog_ids`, list the models via GET /catalogs/{catalog_id}/brands/{brand_id}/models and send the picked one as the item's `model`. Responses are publicly cacheable for 1 hour. **Response 200:** Brands with a model picker. Fields: `data` (array — `id` integer, `title` string nullable, `catalog_ids` array of integer). ### Colors ## GET /api/public/v1/colors — List colors Returns all Vinted colors. Titles are localized to the language of the given `country` (defaults to English). Responses are publicly cacheable for 1 hour. **Query parameters:** - `country` (string, optional) — ISO 3166-1 alpha-2 country code (e.g. "FR") used to localize titles. Defaults to English when omitted or unknown. **Response 200:** List of colors. Fields: `data` (array) — `id` (integer), `title` (string), `hex` (string, nullable), `code` (string, nullable). ### Video game ratings ## GET /api/public/v1/video-game-ratings — List video game ratings Returns all Vinted video game ratings (e.g. PEGI 3, PEGI 7). Titles are localized to the language of the given `country` (defaults to English). Responses are publicly cacheable for 1 hour. **Query parameters:** - `country` (string, optional) — ISO 3166-1 alpha-2 country code (e.g. "FR") used to localize titles. Defaults to English when omitted or unknown. **Response 200:** List of video game ratings. Fields: `data` (array) — `id` (integer), `title` (string). ### Items Item object fields (shared by list/get/create/update responses): `id` (uuid), `vinted_account_id` (uuid), `vinted_id` (integer, nullable — set once imported to Vinted), `status` (string — e.g. `pending`, `imported`), `title` (string, nullable), `description` (string, nullable), `price` (decimal string, nullable), `currency` (string, nullable), `catalog_id` (integer, nullable), `brand_id` (integer, nullable), `brand` (string, nullable), `size_id` (integer, nullable), `status_id` (integer, nullable — Vinted condition id), `color_ids` (array of integer), `package_size_id` (integer, nullable), `video_game_rating_id` (integer, nullable), `isbn` (string, nullable), `manufacturer` (string, nullable), `manufacturer_labelling` (string, nullable), `is_unisex` (boolean, nullable), `disposal_conditions` (boolean), `measurement_length` (integer, nullable), `measurement_width` (integer, nullable), `item_attributes` (array of object), `model` (object, nullable — `name`, `suggestion`, `metadata`: `collection_id`/`model_id`), `parcel` (object, nullable — `dimensions`: `height`/`length`/`width` in cm, `weight` in kg), `photos` (array — `image_no` integer, `url` string nullable), `sku` (string, nullable), `location` (string, nullable), `purchase_price` (decimal string, nullable), `minimum_offer_price` (decimal string, nullable), `counter_offer_price` (decimal string, nullable), `accept_offers_enabled` (boolean), `messages_enabled` (boolean), `restock_enabled` (boolean, nullable), `restock_quantity` (integer, nullable), `supplier_link` (string, nullable), `scheduled_publish_at` (ISO8601 string, nullable — when the item is due to publish to Vinted, set via POST /items/:id/schedule). ## GET /api/public/v1/items — List items Returns the authenticated user's Vinted items with keyset pagination. **Query parameters:** - `status` (string, optional, enum: `pending`, `imported`, `all`) — Filter by import status: pending (default), imported, or all. - `account_id` (uuid, optional) — Restrict results to a specific Vinted account UUID. - `limit` (integer, optional) — Page size (1–100, default 25). - `cursor` (string, optional) — Opaque keyset cursor from a previous response's `next_cursor`. **Response 200:** Paginated list of items. Fields: `data` (array of item objects, see above), `has_more` (boolean), `next_cursor` (string, nullable). ## POST /api/public/v1/items — Create item Creates a pending (source: dotb) item for the authenticated user. Photos are not part of this request; upload them afterwards via POST /items/:id/photos. **Request body:** (JSON) - `vinted_account_id` (uuid, required) — UUID of the Vinted account on Dotb to associate the item with. - `title` (string, required) — Item title (5–100 chars, max 6 non-title-case capital letters). - `description` (string, required) — Item description (5–2000 chars). - `price` (decimal string, required) — Listing price, e.g. `"29.90"`. - `currency` (string, required) — ISO 4217 currency code, e.g. `"EUR"`. - `catalog_id` (integer, required) — Vinted catalog (category) id. - `brand_id` (integer, optional) — Existing Vinted brand id (look it up via GET /brands). Takes priority over `brand` — the canonical name is resolved and returned automatically. To create an item with no brand, pass the special `brand_id` 1 ("no_brand"). - `brand` (string, optional) — Brand name for a new/custom brand; for an existing brand pass brand_id which the API resolves to the name. - `color_ids` (array of integer, optional) — Up to 2 Vinted color ids. - `size_id` (integer, optional) — Vinted size id. - `status_id` (integer, optional) — Vinted condition id. - `package_size_id` (integer, optional) — Vinted package size id. Leave it empty for a catalog with `parcel_measurements` and send `parcel` instead. - `video_game_rating_id` (integer, optional) — Vinted video game rating id (see /video-game-ratings). - `isbn` (string, optional) — ISBN (books/media). - `manufacturer` (string, optional) — Manufacturer. - `manufacturer_labelling` (string, optional) — Manufacturer labelling info. - `is_unisex` (boolean, optional) — Unisex flag. - `disposal_conditions` (boolean, optional) — Disposal conditions acknowledgement. - `measurement_length` (integer, optional) — Length (cm). - `measurement_width` (integer, optional) — Width (cm). - `item_attributes` (array of object, optional) — Catalog-specific attribute selections, each `{"code": "skirt_length", "ids": [392]}` with option ids from the catalog's `attributes` in GET /catalogs/{catalog_id}/options. Skirts (catalog 5523) require `skirt_length` (Mini, Knee-length, Midi, Maxi or Asymmetrical): Vinted will neither draft nor publish a skirt without it, so POST/PATCH /items return 422 when it is missing or not one of the listed options. On PATCH the array replaces the stored one whole. - `model` (object, optional) — Model picked for a catalog + brand that has a model picker (see GET /catalogs/{catalog_id}/brands/{brand_id}/models): `metadata` with the row's `collection_id` and `model_id` (when set), optional `name`. `collection_id` 1 means "I don't know the model"; 2 means "my model isn't listed", with the model's name in `suggestion`. On PATCH it replaces the stored model whole. - `parcel` (object, optional) — Measured parcel, for a catalog whose GET /catalogs/{catalog_id}/options returns `parcel_measurements`: `{"dimensions": {"height": 10, "length": 100, "width": 25}, "weight": 4}`, centimetres and kilograms, all positive numbers. Vinted derives the package size from it when the extension publishes. A parcel over Vinted's shipping limits (`parcel_measurements.limits` in GET /catalogs/{catalog_id}/options: longest side ≤ 100 cm, height + length + width ≤ 250 cm, weight < 31 kg) is rejected with 422, one message per broken limit. On PATCH it replaces the stored parcel whole. - `vinted_account_id`, plus Dotb-only inventory/automation fields (not sent to Vinted): `sku` (string, optional), `location` (string, optional), `purchase_price` (decimal string, optional — your cost price, not shown to buyers), `minimum_offer_price` (decimal string, optional), `counter_offer_price` (decimal string, optional), `accept_offers_enabled` (boolean, optional — allow buyers to make offers), `messages_enabled` (boolean, optional — allow buyers to send messages), `supplier_link` (string, optional — item source URL, https:// is prepended when no scheme is given), `restock_enabled` (boolean, optional — enable auto-restock), `restock_quantity` (integer, optional — -1 = unlimited, range -1..32767). **Response 201:** Created item (same item object shape as List items). Note the status is **201**, not 200. ## GET /api/public/v1/items/{id} — Get item Returns a single item by id. Returns 404 if the item belongs to another user. **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Response 200:** Item (same item object shape as List items). ## PATCH /api/public/v1/items/{id} — Update item Updates a pending (dotb-created) item. Only items without a vinted_id are editable; imported items return 404. Merges data fields. Photos are managed separately via POST /items/:id/photos. **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Request body:** (JSON) Same fields as Create item, all optional — merges into the existing item. **Response 200:** Updated item (same item object shape as List items). ## DELETE /api/public/v1/items/{id} — Delete item Permanently deletes a pending (dotb-created) item and its uploaded photos, and cancels any scheduled publish it still has. Only items without a vinted_id can be deleted; once an item is on Vinted it returns 404, because this endpoint cannot take the listing down from Vinted and deleting only the Dotb record would be misleading. **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Response 200:** Fields: `id` (uuid), `status` (`"deleted"`). **404** if the item is unknown, owned by another user, or already imported to Vinted. ## POST /api/public/v1/items/{id}/photos — Upload an item photo Uploads a single image file (multipart/form-data, field `file`) and appends it to a pending item's photos. Call it once per photo. Only items without a vinted_id are editable; imported items return 404. Max file size is ~8MB (Plug.Parsers default). **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Request body:** (multipart/form-data) - `file` (file, required) — The image file (jpeg/png/webp). **Response 201:** Updated item (same item object shape as List items) including the new entry in `photos`. Note the status is **201**, not 200. **409** on a photo slot conflict — two uploads for the same item raced; retry the upload. ### Scheduled publishes Publishing a Dotb-created item to Vinted is always *scheduled*: you pick a time, and the browser extension drives Vinted's own upload form when that time comes. A schedule needs a connected, publish-capable extension on the item's Vinted account **at schedule time**; once queued, it survives the extension going offline, a closed tab, and a deploy. Scheduled publish object fields (returned by List scheduled publishes): `id` (string — schedule id), `item_id` (uuid), `vinted_account_id` (uuid), `status` (string — see below), `publish` (boolean — true went/goes live, false leaves a Vinted draft), `scheduled_at` (ISO8601 string), `finished_at` (ISO8601 string, nullable), `attempt` (integer), `max_attempts` (integer), `vinted_id` (integer, nullable — the created Vinted listing, set as soon as the draft exists), `error` (object, nullable — `code` and `detail`, present only after a failed attempt). `status` values: `waiting` (queued, not started — includes publishes queued behind another on the same account), `running`, `retrying`, `done` (published; `vinted_id` is the listing), `failed` (gave up — see `error`), `cancelled` (cancelled before it ran), `draft_orphaned` (failed *after* the Vinted draft was created, so a draft exists on Vinted but never went live). `error.code` is one of `ai_photos`, `rejected`, `no_photos`, `missing_attributes` (a required attribute such as a skirt's `skirt_length` is not filled in — PATCH the item, then schedule it again), `validation` (Vinted refused to publish the draft for a field it lacks — `error.detail` has Vinted's own messages; complete the draft on Vinted, then publish it again), `extension_offline`, `extension_error`, `timeout`, `not_pending`, `server_error`, `unknown`. ## GET /api/public/v1/scheduled-publishes — List scheduled publishes Returns the authenticated user's scheduled publishes — upcoming, running and finished — newest scheduled time first, with keyset pagination. Poll this to find out how a schedule turned out. History is pruned after 15 days. Requires the `items_read` scope. **Query parameters:** - `limit` (integer, optional) — Page size (1–100, default 25). - `cursor` (string, optional) — Opaque keyset cursor from a previous response's `next_cursor`. **Response 200:** Paginated list. Fields: `data` (array of scheduled publish objects, see above), `has_more` (boolean), `next_cursor` (string, nullable). ## POST /api/public/v1/items/schedule — Schedule publishes in bulk Schedules up to 250 pending items in one call. Each entry carries its own `scheduled_at` and `publish` flag — the same payload the single-item endpoint takes, with the id moved into the body — so you choose the spacing between publishes yourself. Prefer this over looping the single-item endpoint: the per-token rate limit is 60 requests/minute. Requires the `items_write` scope. Two classes of failure, deliberately different: - A **malformed request** is rejected whole with **422** and **nothing is scheduled** — `items` missing, empty or over 250 entries, a duplicate `id`, a missing or non-ISO8601 `scheduled_at`, or a non-boolean `publish` (a string `"false"` is rejected here, unlike the single-item endpoint). Validation halts on the first bad entry, and `details` keys it by position, e.g. `"items[3].scheduled_at"`. - A **per-item problem** does not fail the batch: the response is **200** with one row per entry. An all-failed batch is still a 200, so check `failed_count` rather than the status. **Request body:** (JSON) - `items` (array of objects, required, 1–250 entries) — ids must be unique within the batch. - `id` (uuid, required) — Item UUID. - `scheduled_at` (ISO8601 string, required) — When to publish this item, with a UTC offset. A past time publishes as soon as possible. - `publish` (boolean, optional, default `true`) — `true` takes the listing live; `false` leaves an unpublished Vinted draft. **Response 200:** Fields: `scheduled_count` (integer), `failed_count` (integer), `results` (array — one row per entry, **in request order**). Each row has `id` (uuid) and `status` (`"scheduled"` or `"failed"`). A scheduled row also has `vinted_account_id` (uuid), `scheduled_at` (ISO8601 string — when the publish will actually run, so a past time comes back clamped to now) and `publish` (boolean). A failed row has `error` with `code` and `detail`, where `code` is one of: `not_found` (not one of your pending items, or already on Vinted), `no_photos` (upload at least one photo first), `extension_offline` (no publish-capable extension on that item's Vinted account), `already_scheduled` (cancel the existing schedule first). ## POST /api/public/v1/items/{id}/schedule — Schedule a publish Queues a pending item to be published to Vinted at `scheduled_at`. The item's photos are published as-is — this version applies no image processing. Only pending items (created through Dotb and not yet on Vinted) can be scheduled, and an item can hold one schedule at a time — cancel the existing one first. Requires the `items_write` scope. **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Request body:** (JSON) - `scheduled_at` (ISO8601 string, required) — When to publish, with a UTC offset. A past time publishes as soon as possible. - `publish` (boolean, optional, default `true`) — `true` takes the listing live; `false` leaves an unpublished Vinted draft. **Response 201:** Fields: `item_id` (uuid), `vinted_account_id` (uuid), `scheduled_at` (ISO8601 string — when the publish will actually run, so a past time comes back clamped to now), `publish` (boolean), `status` (`"scheduled"`). Note the status is **201**, not 200. **404** if the item is unknown, owned by another user, or already on Vinted. **409** when no publish-capable extension is connected for the item's Vinted account, or the item already has a schedule. **422** when `scheduled_at` is missing/unparseable, or the item has no photo. ## DELETE /api/public/v1/items/{id}/schedule — Cancel a scheduled publish Cancels the item's pending schedule. Requires the `items_write` scope. **Query parameters:** - `id` (uuid, required, path) — Item UUID. **Response 200:** Fields: `item_id` (uuid), `vinted_account_id` (uuid), `status` (`"cancelled"`). **404** when there is nothing to cancel — either it was never scheduled, or the publish has already started. ### Orders Every `/orders/...` path takes the Dotb order UUID (`id`) — never a Vinted id. Vinted's own ids travel as response fields only (`vinted_transaction_id`, `vinted_conversation_id`), and there is no way to look an order up by them: filter/paginate `GET /orders` and match client-side. Order object fields (shared by list/get responses): `id` (uuid), `title` (string, nullable), `status` (string, nullable — raw Vinted status), `normalized_status` (string, nullable — see the `status` filter enum on List orders), `status_updated_at` (ISO8601 datetime, nullable), `order_date` (ISO8601 datetime, nullable), `shipping_deadline_date` (ISO8601 datetime, nullable), `ready_for_pickup_at` (ISO8601 datetime, nullable), `currency` (string, nullable), `original_price` (decimal string, nullable), `subtotal` (decimal string, nullable), `subtotal_vat` (decimal string, nullable), `shipping` (decimal string, nullable), `shipping_vat` (decimal string, nullable), `service_fee` (decimal string, nullable), `vat_rate` (decimal string, nullable), `payout` (decimal string, nullable), `gross_profit` (decimal string, nullable), `total_cost` (decimal string, nullable), `item_count` (integer), `invoice_id` (string, nullable), `note` (string, nullable — your private note, never sent to Vinted, max 500 characters; set it with `PATCH /orders/{id}/note`), `carrier_name` (string, nullable), `tracking_code` (string, nullable), `tracking_url` (string, nullable), `transaction_user_status` (string, nullable), `vinted_transaction_id` (integer, nullable), `vinted_conversation_id` (integer, nullable — Vinted conversation/message-thread id tied to the order; null when none was captured). Always-present base fields (not include-gated): - `shipping_address` (object) — `name`, `line1`, `line2`, `city`, `postal_code`, `country_code` (all string, nullable). - `shipping_label_url` (string, nullable) — null until the shipping label has been fetched. Optional blocks, expanded via the `include` query parameter (`items`, `buyer`, `account`): - `account` (object) — `id` (uuid), `login` (string, nullable), `vinted_id` (integer, nullable), `country_code` (string, nullable), `has_company` (boolean). - `buyer` (object, nullable) — `login` (string, nullable), `name` (string, nullable), `email` (string, nullable), `vinted_id` (integer, nullable), `country_code` (string, nullable). - `items` (array) — `id` (uuid — use it to PATCH the item), `vinted_item_id` (string, nullable), `vinted_id` (integer, nullable), `title` (string, nullable), `catalog_id` (integer, nullable), `sku` (string, nullable), `location` (string, nullable), `thumbnail_url` (string, nullable), `supplier_link` (string, nullable), `original_price` (decimal string, nullable), `selling_price` (decimal string, nullable), `purchase_price` (decimal string, nullable). ## GET /api/public/v1/orders — List orders Returns the authenticated user's Vinted orders with keyset pagination. **Query parameters:** - `status` (string, optional, enum: `payment_received`, `label_ordering`, `label_failed`, `label_sent`, `packed`, `shipped`, `ready_for_pickup`, `delivered`, `completed`, `returning`, `suspended`, `cancelled`) — Filter by normalized_status. Invalid values are ignored. - `account_id` (uuid, optional) — Restrict results to a specific Vinted account UUID. - `from` (string, optional) — Inclusive lower bound on order_date. Accepts a date (`YYYY-MM-DD`) or full ISO8601 timestamp; a date-only value covers the whole day (from 00:00:00 UTC). Invalid values are ignored. - `to` (string, optional) — Inclusive upper bound on order_date. Accepts a date (`YYYY-MM-DD`) or full ISO8601 timestamp; a date-only value covers the whole day (through 23:59:59 UTC). Invalid values are ignored. - `limit` (integer, optional) — Page size (1–100, default 25). - `cursor` (string, optional) — Opaque keyset cursor from a previous response's `next_cursor`. A cursor is bound to the `sort` it was created with — changing `sort` mid-pagination invalidates it, so restart from the first page (no cursor) whenever you change `sort`. - `include` (string, optional) — Comma-separated list of optional blocks to expand: `items`, `buyer`, `account`. Unknown tokens are ignored. Absent by default (lean response). `shipping_address` and `shipping_label_url` are always-present base fields (not include-gated). - `sort` (string, optional, enum: `order_date`, `shipping_deadline_date`) — Sort column, fixed direction per column. `order_date` (default) — newest first. `shipping_deadline_date` — soonest deadline first; orders without a deadline sorted last. Invalid/empty values fall back to `order_date`. **Response 200:** Paginated list of orders. Fields: `data` (array of order objects, see above), `has_more` (boolean), `next_cursor` (string, nullable). ## GET /api/public/v1/orders/{id} — Get order Returns a single order by id. Returns 404 if the order belongs to another user. **Query parameters:** - `id` (uuid, required, path) — Order UUID. - `include` (string, optional) — Comma-separated list of optional blocks to expand: `items`, `buyer`, `account`. Unknown tokens are ignored. Absent by default (lean response). `shipping_address` and `shipping_label_url` are always-present base fields (not include-gated). **Response 200:** Order (same order object shape as List orders). ## POST /api/public/v1/orders/{id}/pack — Mark order as packed Marks an order as packed (a Dotb fulfillment status between label_sent and shipped). Only orders currently in `label_sent` can be packed; any other status returns 409. This is a local status change only — nothing is pushed to Vinted. **Query parameters:** - `id` (uuid, required, path) — Order UUID. **Response 200:** Updated order (same order object shape as List orders), with `normalized_status` set to `packed`. **409** if the order is not currently in `label_sent`. ## POST /api/public/v1/orders/{id}/shipping-label — Order the shipping label Orders the Vinted shipping label for an order. Requires the order's Vinted account to have the extension bridge up and running — otherwise the call returns 504 with `details.reason = "bridge_offline"`. It then waits up to ~35s for Vinted to generate the PDF before responding, so it is deliberately slow. Only orders in `payment_received` or `label_failed` can order a label; any other status returns 409. **Not idempotent, and rate limited**: once the request reaches Vinted the label is ordered even if this call then times out, so an order that just ordered a label is barred from ordering another for a few minutes (429 + `Retry-After`). Do not blindly retry — re-read the order first. **Query parameters:** - `id` (uuid, required, path) — the Dotb order UUID (the order's `id`), NOT `vinted_transaction_id`. The Vinted transaction is resolved server-side from the order. **Request body:** (optional) - `label_type` (string, optional, enum: `printable`, `digital`, default `printable`) — Preferred label format. Applied only when the carrier actually offers a choice; otherwise Vinted's default for the method the buyer paid for is used. A preference that isn't on offer is not an error — check `label_type` in the response. **Response 200:** `label_ready` (boolean — whether the PDF was generated before the wait budget ran out; `false` still means the label WAS ordered, poll `GET /orders/{id}` until `shipping_label_url` is set), `label_type` (string, nullable — what was actually sent to Vinted; `null` means Vinted's own default), `order` (the order object, same shape as List orders). **409** if the status can't order a label. **422** for an unknown `label_type`. **429** (`details.reason` `label_recently_ordered`, plus `details.retry_after_seconds` and a `Retry-After` header) when a label was already ordered for this order minutes ago. **504** (`details.reason` `bridge_offline`) when the extension bridge isn't running for that account. **503** when the bridge is busy or Vinted refused — `details.reason` is `busy`, `captcha`, or `vinted_error`. ## PATCH /api/public/v1/orders/{id}/note — Set the order note Sets or clears your private note on an order — the same note shown in the dashboard. It is stored in Dotb only and never sent to Vinted. The `note` key is required: send `null` or `""` to clear the note. A note longer than 500 characters, a missing `note` key or a non-string value returns 422 with the reason under `details.note`; the stored note is left unchanged. Requires the `orders_write` scope. **Query parameters:** - `id` (uuid, required, path) — Order UUID. **Request body:** - `note` (string, nullable, required, max 500 characters) — The new note. Replaces the current note entirely. `null` or `""` clears it. **Response 200:** Updated order (same order object shape as List orders). **404** if the order is not found or belongs to another user. **422** (`type` `validation_error`, `details.note`) when `note` is missing, not a string, or longer than 500 characters. ## PATCH /api/public/v1/orders/{order_id}/items/{id} — Update an order item Updates the inventory fields of an item within an order — the same fields editable in the dashboard. Only the fields you send are changed; setting `purchase_price` recalculates the order's profit totals. Returns 404 if the order or item is not found or belongs to another user. Requires the `orders_write` scope. **Query parameters:** - `order_id` (uuid, required, path) — Order UUID. - `id` (uuid, required, path) — Order item UUID (the `id` field of an order's `items` entry). **Request body:** (JSON) All fields optional; only sent fields are changed. - `sku` (string, nullable) — Seller SKU for inventory. - `location` (string, nullable) — Storage location. - `supplier_link` (string, nullable) — Source URL; `https://` is prepended when no scheme is given. - `purchase_price` (decimal string, nullable) — Your cost price; recalculates order profit totals. **Response 200:** The updated order item (same shape as an order's `items` entry).