2026-08-09 22:47:43 +02:00
package httpapi
// OpenAPI 3.0 for the public /api/v1 surface (API-key auth).
// Served at GET /api/v1/openapi.yaml; rendered by the marketing /docs page.
var v1OpenAPIYAML = [] byte ( `openapi: 3.0.3
info:
title: Descrybe Public API
version: '1.0'
description: |
Public catalog, feed, and product-processing API.
## Base URL
The public API is hosted by Descrybe (customers do not self-host this surface).
- Production: https://descrybe.io/api/v1
OpenAPI document: GET https://descrybe.io/api/v1/openapi.yaml
## Authentication
All /api/v1 operations require a company API key except:
Document-level security is BearerAuth OR ApiKeyAuth (same key value).
Do not send dashboard session cookies or CSRF tokens to /api/v1.
### Security schemes (components.securitySchemes)
- BearerAuth - HTTP bearer. Authorization: Bearer dk_your_key (preferred)
- ApiKeyAuth - header X-API-Key: dk_your_key
When both headers are set, Bearer wins. Full key values are never embedded in
this YAML. RapiDoc Try-it: paste a key, or when logged into the docs page use
"Use my API key" (coordinates with the in-app authorize helper).
### Create a key in the app
1. Sign in at https://descrybe.io
2. Open Settings -> API keys (/settings?tab=api-keys)
3. Company admins create a key via dashboard POST /api/api-keys
(session cookie + CSRF; not this public OpenAPI surface). The secret is
shown once and starts with dk_.
4. Store it securely. Later list/revoke shows only key_prefix (first 10
characters). Revoked keys fail auth immediately.
2026-08-16 16:57:36 +02:00
### Cutover / migration (reissue)
API keys from the previous Descrybe platform were not migrated. After
cutover, integrations must create a new dk_ key in Settings -> API keys
2026-08-09 22:47:43 +02:00
(or Use my API key on /docs). Pre-cutover secrets return the same HTTP 401
Unauthorized as unknown keys — there is no separate “legacy key” error.
### 401 Unauthorized
Missing, empty, unknown, revoked, or non-migrated (pre-cutover) keys return
HTTP 401 from RequireAPIKey with the legacy coded envelope:
{ "error": { "code": "unauthorized", "message": "Unauthorized" } }
See components.responses.Unauthorized (schema LegacyAPIError). Reissue via
Settings -> API keys (/settings?tab=api-keys).
### 403 Forbidden
Bad API keys on /api/v1 never return 403 (always 401). Tenant scope comes
from the key; cross-company resources typically 404. HTTP 403 appears on
dashboard /api/* session routes (admin required, CSRF mismatch) under
Team/Admin tags (SessionCookie + CSRFHeader) - not this public key surface.
### Rate limits
Heavy mutations are limited to 30 requests per minute per company
(in-process per API replica; not shared across replicas). Counts HTTP
requests, not products inside a bulk body. Limited POST paths:
- /products/process, /process, /process/{id}/retry
- /feeds/{id}/sync, /feeds/{id}/extract-schema, /feeds/{id}/sync-process-sample
- /export-feeds/{id}/generate, /export-feeds/{id}/export-products, /export-feeds/{id}/rotate-token (admin)
Over limit: HTTP 429, header Retry-After: 60, body
{ "error": "rate limit exceeded" } (see TooManyRequests). Ordinary GETs and
other mutations are outside this HTTP budget (process starts may still return
402 for plan/credits).
## Quick curl
curl -s -H "Authorization: Bearer dk_your_key" \
"https://descrybe.io/api/v1/products?page=1&limit=1"
Local Go API (default listen from README):
curl -s -H "Authorization: Bearer dk_your_key" \
"http://localhost:28471/api/v1/products?page=1&limit=1"
Health: GET /api/v1/health (also /healthz and /readyz on the API host).
## Processing contracts (dual-mode)
Two separate surfaces — do not mix bodies or response envelopes:
1. **Legacy public process (source of truth for integrations)**
- POST /products/process with body items[].ean
- GET /products/process/{id}
- Envelope: HTTP 200 { data: { process_id, … } } (and completed items[])
- Matches legacy Descrybe /api/v1/products/process
- Handler also accepts raw_product_ids as an alternate body on this path
- Plan gates (credits / product limit / AI / EPREL / feature flags) run before
EnsureRaw catalog writes on the items[].ean path; blocked starts return HTTP 402
2. **Internal / dashboard-style jobs**
- POST /process with raw_product_ids (same body as POST /api/processing/jobs)
- GET /process, GET /process/{id}, cancel/terminate/retry
- Flat JSON (no data wrapper); 202 Accepted on start/retry
- Plan gates return HTTP 402 with PlanGateError (error + code + upgrade_url)
### Dual IDs (do not confuse)
- GET /products data[].id = processed_products.id (enriched row)
- GET /products data[].raw_product_id = raw_products.id (use this for raw_product_ids)
- POST ... raw_product_ids[] must be raw_products.id — never PresentProduct.id
- GET /products/process/{id} COMPLETED items[].id = processed_products.id (legacy);
additive processed_product_id (same as id) and raw_product_id (raw_products.id)
Note: Dashboard JSON under /api/* uses session cookies + CSRF and is separate
from this public API-key surface. Other legacy path aliases
(/categories/create, /attributes/create, /campaigns) appear next to canonical paths.
servers:
- url: https://descrybe.io/api/v1
description: Production (Descrybe-hosted)
- url: http://localhost:28472/api/v1
description: Local web (Vite proxy to Go API)
- url: http://localhost:28471/api/v1
description: Local API (default Go listen address)
security:
- BearerAuth: []
- ApiKeyAuth: []
tags:
- name: Health
- name: Products
- name: Categories
- name: Attributes
- name: Feeds
- name: Export feeds
- name: Campaigns
description: Seasonal content calendar (legacy /campaigns aliases)
- name: Processing
- name: Team
description: Dashboard session routes under /api (not API-key)
- name: Admin
description: Platform-admin dashboard routes under /api (not API-key)
paths:
/health:
get:
tags:
- Health
security: []
summary: Liveness (same payload as /healthz)
description: |
Returns process liveness plus cutover flags (maintenance, read_only, hypercare).
Host-level probes /healthz and /readyz share the same flag fields;
/readyz additionally reports database checks.
responses:
'200':
description: Liveness OK. No API key required. Same flag fields as host /healthz (status, service,
maintenance, read_only, hypercare).
content:
application/json:
schema:
$ref: "#/components/schemas/HealthStatus"
example:
status: ok
service: api
maintenance: false
read_only: false
hypercare: false
'500':
description: Unexpected failure building the health payload (rare process fault).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: health check failed
/openapi.yaml:
get:
tags:
- Health
security: []
summary: This OpenAPI document
responses:
'200':
description: This OpenAPI document. Served with Cache-Control and ETag; gzip when Accept-Encoding
allows.
content:
application/yaml:
schema:
type: string
example: |
openapi: 3.0.3
info:
title: Descrybe Public API
version: "1.0"
'304':
description: Not Modified — request If-None-Match matched the document ETag. Empty body.
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
/products:
get:
tags:
- Products
summary: List products
description: |
Legacy-compatible public list. Envelope is { data, meta } (not flat products/offset).
Query params match legacy: page, limit (default 25), status, search, sortBy, sortOrder, feedId.
Each row matches PresentProduct (id = processed_products.id, raw_product_id = raw_products.id,
product_id, name, category, status, feed_id, quality_score, quality_grade, created_at, updated_at).
For POST /products/process or POST /process dual-mode bodies, pass raw_product_id — not id.
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/LegacyLimit"
- in: query
name: status
schema:
type: string
enum:
- all
- needs_review
- processed
- completed
- error
- processing
- unprocessed
default: all
description: |
Filter by product status. all (default) returns every status. needs_review also matches
legacy status processed (pre-P0-8 AI review queue). Use completed after Accept enrichment.
- in: query
name: search
schema:
type: string
maxLength: 200
description: |
Free-text search over product name / product_id. Alias q is also accepted.
Omit or empty to skip text filtering. Max ~200 characters.
- in: query
name: sortBy
schema:
type: string
enum:
- updatedAt
- createdAt
- name
default: updatedAt
description: |
Sort column for the product list. One of updatedAt (default), createdAt, or name.
- in: query
name: sortOrder
schema:
type: string
enum:
- asc
- desc
default: desc
description: |
Sort direction. asc or desc (default desc). Combined with sortBy.
- in: query
name: feedId
schema:
type: string
format: uuid
description: |
Restrict results to products from this input feed. UUID format.
Alias feed_id is also accepted. Omit to include all feeds.
responses:
'200':
description: "Paged processed products for the API-key company. Returned after ListProcessedProductsDetailed\
\ succeeds. Envelope is data[] + meta (page, limit, total, totalPages)."
content:
application/json:
schema:
$ref: "#/components/schemas/ProductListResponse"
example:
data:
- id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
product_id: SKU-1001
name: Wireless earbuds
category: electronics/audio
status: completed
feed_id: 22222222-2222-2222-2222-222222222222
quality_score: 72
quality_grade: C
created_at: '2026-08-01T10:15:00Z'
updated_at: '2026-08-03T14:22:11Z'
meta:
page: 1
limit: 25
total: 1284
totalPages: 52
'400':
description: Client-facing catalog validation on filters (for example an invalid feed id). handleV1ListProducts
uses v1Err validation_error.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: invalid feed id
'500':
description: Unexpected database/list failure in handleV1ListProducts.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
/products/quality:
get:
tags:
- Products
summary: List product quality scores
description: |
Legacy-compatible quality listing with { data, meta }. Defaults status=completed.
Optional min_score filters rows after scoring.
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/LegacyLimit"
- in: query
name: status
schema:
type: string
enum:
- all
- needs_review
- processed
- completed
- error
- processing
- unprocessed
default: completed
description: |
Filter by product status. Default completed (quality scores are most useful after enrichment).
all returns every status. Same values as GET /products status.
- in: query
name: search
schema:
type: string
maxLength: 200
description: |
Free-text search over product name / product_id. Alias q also accepted. Omit to skip.
- in: query
name: min_score
schema:
type: integer
minimum: 0
maximum: 100
description: |
Minimum quality score (0-100 inclusive). Rows below this value are excluded after scoring.
Omit for no score floor.
- in: query
name: feedId
schema:
type: string
format: uuid
description: |
Restrict to products from this input feed UUID. Alias feed_id also accepted.
responses:
'200':
description: Quality rows for processed products (default status=completed). Returned after
list+score. min_score filters in-process after scoring. Envelope data + meta (page, limit,
total).
content:
application/json:
schema:
$ref: "#/components/schemas/ProductQualityListResponse"
example:
data:
- id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
product_id: SKU-1001
name: Wireless earbuds
quality_score: 72
quality_grade: C
quality_checks:
title: true
description: true
attributes: false
meta:
page: 1
limit: 25
total: 410
'400':
description: Query min_score is present but not an integer.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: invalid min_score
'500':
description: Unexpected list failure in handleV1ListProductQuality.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
/products/reset:
post:
tags:
- Products
summary: Reset products to unprocessed
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- product_ids
properties:
product_ids:
type: array
minItems: 1
items:
type: string
format: uuid
description: |
Processed (or raw, when kind=raw) product UUIDs to return to unprocessed.
Non-empty array of UUID strings.
kind:
type: string
enum:
- processed
- raw
default: processed
description: |
Which table the ids refer to. processed (default) resets enriched products;
raw targets raw_products rows instead.
example:
product_ids:
- a1b2c3d4-e5f6-7890-abcd-ef1234567890
- b2c3d4e5-f6a7-8901-bcde-f12345678901
kind: processed
responses:
'200':
description: Selected products returned to unprocessed. Returned when ResetProductsToUnprocessed
commits. Requires company-admin capability (API keys use role api and pass).
content:
application/json:
schema:
type: object
required:
- success
- reset_count
- message
properties:
success:
type: boolean
reset_count:
type: integer
message:
type: string
example:
success: true
reset_count: 12
message: 12 product(s) returned to unprocessed state
'400':
description: Invalid JSON, invalid product_ids UUID, empty product_ids, over max batch, or catalog.ClientError
from reset.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: product_ids is required
'403':
description: Caller role is neither admin nor api (requireCompanyAdmin). Valid company API keys
use role api and do not hit this.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'500':
description: Unexpected reset failure after validation.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not reset products
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/products/ {id}:
parameters:
- $ref: "#/components/parameters/ID"
get:
tags:
- Products
summary: Get processed product
description: |
Shared dashboard handler — flat ProcessedProduct JSON (not a legacy { data } envelope).
List endpoints use { data, meta }.
responses:
'200':
description: Single processed product as a flat object (no data wrapper). Returned when GetProcessedProduct
finds the id for this company.
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessedProduct"
example:
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
product_id: SKU-1001
name: Wireless earbuds
status: completed
'400':
description: Path id is not a UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Product not found for this company (or wrong tenant).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
patch:
tags:
- Products
summary: Update processed product
description: "Flat ProcessedProduct JSON (not a legacy { data } envelope)."
requestBody:
content:
application/json:
schema:
type: object
description: Partial update — only send fields to change
properties:
processed_name:
type: string
description: Enriched display title after AI/manual edit
processed_description:
type: string
description: Enriched long description text
status:
type: string
enum:
- needs_review
- processed
- completed
- error
- processing
- unprocessed
description: Product workflow status. Use completed after accepting enrichment.
category:
type: string
description: Category path or unique_id string stored on the product
attributes:
type: object
additionalProperties: true
description: Source/raw attribute map (string keys to scalar or list values)
processed_attributes:
type: object
additionalProperties: true
description: Enriched attribute map after processing
example:
processed_name: Sony WH-1000XM5 Wireless Noise Cancelling Headphones Black
status: completed
attributes:
color: Black
brand: Sony
battery_life_hours: '30'
responses:
'200':
description: Product updated. Flat processed product JSON when UpdateProcessedProduct succeeds.
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessedProduct"
example:
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
product_id: SKU-1001
name: Wireless earbuds Pro
status: completed
'400':
description: Invalid JSON, invalid id, or catalog.ClientError / ClientOrLog on update.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not update product
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Resource id not found for this API-key company (or wrong tenant).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/products/process:
post:
tags:
- Processing
summary: Start processing by EAN (legacy public contract)
description: |
Legacy Descrybe public API (handleV1StartProcess). Upserts raw products from
items[].ean (GTIN), enqueues one processing job, returns HTTP 200 with a data envelope.
Primary body: items[].ean. Alternate body on the same handler: raw_product_ids
(raw_products.id UUID list — not GET /products data[].id). Prefer items for public
integrations. On items[].ean, assertV1ProcessGates runs before EnsureRaw so plan/credit
failures cannot spam catalog writes (HTTP 402 legacy coded envelope).
Not an alias of POST /process (flat ProcessingJob / 202). Do not mix envelopes.
requestBody:
$ref: "#/components/requestBodies/LegacyStartProcessByEAN"
responses:
'200':
description: "Legacy process job accepted. Always HTTP 200 (not 202) with data.process_id when\
\ enqueue succeeds. Prefer items[].ean; raw_product_ids alternate body is accepted on the\
\ same path."
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyProcessStartEnvelope"
example:
data:
process_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: PENDING
processing_type: full
'400':
description: Missing body, invalid processing_type, items without ean, invalid raw_product_id,
empty items, or other validation_error from handleV1StartProcess / v1ErrFromProcessing.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: '''items'' array is required'
'402':
description: |
Plan gate blocked starting processing (billing credits/limits/AI/EPREL/feature flags).
v1ErrFromProcessing maps these to HTTP 402 with a coded legacy envelope.
Codes: insufficient_credits, product_limit, ai_requires_upgrade, eprel_requires_upgrade,
plan_gate (message feature_disabled when the platform feature flag is off).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
examples:
insufficient_credits:
summary: Credits
value:
error:
code: insufficient_credits
message: Insufficient credits
feature_disabled:
summary: Feature flag
value:
error:
code: plan_gate
message: feature_disabled
"429": { $ref: "#/components/responses/TooManyRequests" }
'500':
description: Enqueue or unexpected internal failure starting the legacy job.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_server_error
message: Internal server error
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/products/process/{id}:
get:
tags:
- Processing
summary: Processing job status by process_id (legacy public contract)
description: |
Poll legacy job status. Path param is process_id from POST /products/process.
Completed jobs return items[] (EAN-keyed enrichment). In-progress and failed
jobs omit items. Not the same shape as GET /process/{id} (flat ProcessingJob).
On COMPLETED items, id is the processed_products UUID (legacy). Additive aliases:
2026-08-16 16:57:36 +02:00
processed_product_id (same value as id), raw_product_id (raw_products.id), and
name (same value as title) for dual-mode clients / scorecards.
2026-08-09 22:47:43 +02:00
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: |
process_id returned by POST /products/process. UUID format. Required.
responses:
'200':
description: "Legacy job poll. Returned when the job exists for this company. status is uppercase;\
\ items[] appear when status is COMPLETED."
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyProcessStatusEnvelope"
examples:
completed:
2026-08-16 16:57:36 +02:00
summary: Completed A1-shaped item
2026-08-09 22:47:43 +02:00
value:
data:
status: COMPLETED
process_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
processing_type: full
items:
2026-08-16 16:57:36 +02:00
- ean: '8606019604493'
2026-08-09 22:47:43 +02:00
id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
processed_product_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
raw_product_id: cccccccc-cccc-cccc-cccc-cccccccccccc
status: processed
2026-08-16 16:57:36 +02:00
category: '50'
category_name: Cookers
title: VOX electric cooker EHT 6020 WG
name: VOX electric cooker EHT 6020 WG
meta_title: VOX electric cooker EHT 6020 WG | 50
meta_description: Affordable electric cooker with four plates and a 65 L fan oven.
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven."
attributes:
brand: Vox
product_model: EHT6020WG
main_image: https://images.example.com/products/vox-eht6020wg.jpg
more_images: null
eprel:
id: "1234567"
label: https://eprel.ec.europa.eu/label/Example
pdf: https://eprel.ec.europa.eu/fiches/Example.pdf
energy_class: A
energy_scale: A-G
2026-08-09 22:47:43 +02:00
total_items: 1
processed_at: '2026-08-04T10:04:12Z'
processing:
summary: In progress
value:
data:
status: PROCESSING
process_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
processing_type: full
'400':
description: Path id is not a UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: invalid id
'404':
description: No processing job with this id for the API-key company.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: not_found
message: Processing job not found
'500':
description: Unexpected failure loading job status.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_server_error
message: Internal server error
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/categories:
get:
tags:
- Categories
summary: List categories
description: "Legacy public contract — { data, meta } with page/limit pagination."
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: |
1-based page index. Integer, default 1, minimum 1.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
description: |
Page size. Integer, default 25, minimum 1, maximum 100.
- in: query
name: search
schema:
type: string
maxLength: 200
description: |
Free-text search over category name / unique_id. Alias of q; either may be sent.
- in: query
name: q
schema:
type: string
maxLength: 200
description: |
Canonical search query (same as search). Omit both to return the full page.
responses:
'200':
description: "Paged categories as data[] + meta. Returned after ListCategories succeeds."
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyCategoriesResponse"
example:
data:
- id: 33333333-3333-3333-3333-333333333333
unique_id: electronics
name: Electronics
created_at: '2026-07-01T08:00:00Z'
updated_at: '2026-07-15T12:00:00Z'
meta:
page: 1
limit: 25
total: 42
totalPages: 2
'500':
description: Unexpected list failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
post:
tags:
- Categories
summary: Create category
description: Requires name and unique_id. parent_id is accepted as an alias of parent_unique_id.
requestBody:
$ref: "#/components/requestBodies/CreateCategory"
responses:
'201':
description: Category created. HTTP 201 with data containing id, unique_id, name.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyCategoryCreateResponse"
example:
data:
id: 33333333-3333-3333-3333-333333333333
unique_id: electronics
name: Electronics
'400':
description: Invalid JSON, missing name/unique_id, duplicate unique_id, bad parent, or other
catalog.ClientError from CreateCategory.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not create category
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/categories/create:
post:
tags:
- Categories
summary: Create category (legacy alias)
description: "Alias of POST /categories. Same body and { data } response."
requestBody:
$ref: "#/components/requestBodies/CreateCategory"
responses:
'201':
description: Category created. HTTP 201 with data containing id, unique_id, name.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyCategoryCreateResponse"
example:
data:
id: 33333333-3333-3333-3333-333333333333
unique_id: electronics
name: Electronics
'400':
description: Invalid JSON, missing name/unique_id, duplicate unique_id, bad parent, or other
catalog.ClientError from CreateCategory.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not create category
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/categories/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
description: |
For GET/PATCH — category UUID. For DELETE — category unique_id slug (e.g. electronics).
Required. Format depends on the method.
get:
tags:
- Categories
summary: Get category by UUID
description: "Flat category JSON (not a legacy { data } envelope). Path id must be the category\
\ UUID."
responses:
'200':
description: Category detail (flat CategoryDetail) when found. Path id is typically unique_id
for v1 delete; shared get handler accepts the mounted id param.
content:
application/json:
schema:
$ref: "#/components/schemas/CategoryDetail"
example:
id: 33333333-3333-3333-3333-333333333333
unique_id: electronics
name: Electronics
'400':
description: Invalid category id.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Category not found for this company.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected get failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: get failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
patch:
tags:
- Categories
summary: Update category by UUID
description: "Flat updated category JSON (not a legacy { data } envelope)."
requestBody:
content:
application/json:
schema:
type: object
description: Partial update — only send fields to change
properties:
name:
type: string
description: Display name shown in the catalog UI
unique_id:
type: string
description: |
Stable slug identifier (lowercase letters, digits, underscores/hyphens).
Changing it may break feed mappings that reference the old id.
parent_unique_id:
type: string
nullable: true
description: Parent category unique_id, or null for a root category
description:
type: string
nullable: true
description: Optional human-readable category description
is_active:
type: boolean
description: When false, category is hidden from active catalog selection
position:
type: integer
description: Sort order among siblings (lower first). Non-negative integer.
example:
name: Consumer Electronics
description: Updated root for consumer devices
is_active: true
position: 0
responses:
'200':
description: Category updated (flat object) when PATCH succeeds.
content:
application/json:
schema:
$ref: "#/components/schemas/CategoryDetail"
example:
id: 33333333-3333-3333-3333-333333333333
unique_id: electronics
name: Consumer Electronics
'400':
description: Invalid JSON or catalog client error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Category not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
delete:
tags:
- Categories
summary: Delete category by unique_id
description: Path id is the category unique_id (legacy public contract).
responses:
'200':
description: Category deleted by unique_id path param. Returned when DeleteCategoryByUniqueID
succeeds. Envelope data.message.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacySuccessMessage"
example:
data:
message: Category deleted successfully
'400':
description: Empty unique_id or catalog.ClientError blocking delete.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid category id
'404':
description: No category with this unique_id for the company.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected delete failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: delete failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/attributes:
get:
tags:
- Attributes
summary: List attributes
description: "Legacy public contract — { data, meta }."
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: |
1-based page index. Integer, default 1, minimum 1.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
description: |
Page size. Integer, default 25, minimum 1, maximum 100.
- in: query
name: search
schema:
type: string
maxLength: 200
description: |
Free-text search over attribute name / key. Alias of q.
- in: query
name: q
schema:
type: string
maxLength: 200
description: |
Canonical search query (same as search). Omit both for an unfiltered page.
- in: query
name: categoryId
schema:
type: string
description: |
Filter by category unique_id slug (e.g. electronics), not UUID.
- in: query
name: sortBy
schema:
type: string
enum:
- name
- attributeKey
- updatedAt
default: updatedAt
description: |
Sort column. name, attributeKey, or updatedAt (default).
- in: query
name: sortOrder
schema:
type: string
enum:
- asc
- desc
default: desc
description: |
Sort direction. asc or desc (default desc).
responses:
'200':
description: "Paged attributes as data[] + meta (presentV1Attribute)."
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAttributesResponse"
example:
data:
- id: 44444444-4444-4444-4444-444444444444
key: color
name: Color
type: text
unit: ""
required: false
category_unique_id: electronics
created_at: '2026-07-01T08:00:00Z'
updated_at: '2026-07-15T12:00:00Z'
meta:
page: 1
limit: 25
total: 18
totalPages: 1
'500':
description: Unexpected list failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
post:
tags:
- Attributes
summary: Create attribute
description: Requires name, attribute_key, value_type, and category_unique_id; links the attribute
to that category.
requestBody:
$ref: "#/components/requestBodies/CreateAttribute"
responses:
'201':
description: Attribute created (and linked when category_unique_id resolves). HTTP 201 data
envelope.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAttributeCreateResponse"
example:
data:
id: 44444444-4444-4444-4444-444444444444
key: color
name: Color
type: text
category_unique_id: electronics
'400':
description: Invalid JSON, missing required fields, or catalog.ClientError on create/link.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: "Missing required fields: name, attribute_key, value_type, category_unique_id"
'404':
description: Link target category was not found (when link step maps to not found).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected create failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not create attribute
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/attributes/create:
post:
tags:
- Attributes
summary: Create attribute (legacy alias)
description: Alias of POST /attributes.
requestBody:
$ref: "#/components/requestBodies/CreateAttribute"
responses:
'201':
description: Attribute created (and linked when category_unique_id resolves). HTTP 201 data
envelope.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAttributeCreateResponse"
example:
data:
id: 44444444-4444-4444-4444-444444444444
key: color
name: Color
type: text
category_unique_id: electronics
'400':
description: Invalid JSON, missing required fields, or catalog.ClientError on create/link.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: "Missing required fields: name, attribute_key, value_type, category_unique_id"
'404':
description: Link target category was not found (when link step maps to not found).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected create failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not create attribute
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/attributes/ {id}:
parameters:
- $ref: "#/components/parameters/ID"
patch:
tags:
- Attributes
summary: Update attribute
description: Flat attribute JSON (attribute_key / value_type fields — not legacy key/type aliases).
requestBody:
content:
application/json:
schema:
type: object
description: Partial update — only send fields to change
properties:
name:
type: string
description: Human-readable attribute label shown in the UI
value_type:
type: string
enum: [string, number, list, multiselect]
description: |
Value shape for this attribute. string, number, list, or multiselect.
unit:
type: string
nullable: true
description: Optional unit label (e.g. W, cm). Null clears the unit.
example:
type: string
nullable: true
description: Sample value for docs/UI hints (e.g. Black)
example:
name: Color
value_type: string
example: Black
responses:
'200':
description: Attribute updated when PATCH succeeds (flat AttributeDetail / shared handler).
content:
application/json:
schema:
$ref: "#/components/schemas/AttributeDetail"
example:
id: 44444444-4444-4444-4444-444444444444
attribute_key: color
name: Colour
'400':
description: Invalid JSON or catalog client error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Attribute not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
delete:
tags:
- Attributes
summary: Delete attribute by UUID
responses:
'200':
description: Attribute deleted by UUID. Returned when DeleteAttribute succeeds. data.message
envelope.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacySuccessMessage"
example:
data:
message: Attribute deleted successfully
'400':
description: Path id is not a UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid attribute id
'404':
description: Attribute not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected delete failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: delete failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/feeds:
get:
tags:
- Feeds
summary: List input feeds
description: |
Legacy public contract — { data: Feed[], meta: { page, limit, total } }.
Accepts page+limit (legacy defaults page=1, limit=25, max 100) or limit+offset.
Each feed includes presentFeed fields plus dual-support v2 keys (feed_type, sync_interval_minutes, options).
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: |
1-based page index for legacy page+limit mode. Integer, default 1, minimum 1.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
description: |
Page size. Integer, default 25, minimum 1, maximum 100. Also used with offset.
- $ref: "#/components/parameters/Offset"
- in: query
name: q
schema:
type: string
maxLength: 200
description: |
Free-text search over feed name / URL. Canonical search param.
- in: query
name: search
schema:
type: string
maxLength: 200
description: |
Alias of q. Same free-text search over feed name / URL.
responses:
'200':
description: Paged feeds with meta (including active_total, mapped_total). Returned after Feeds.List.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedListResponse"
example:
data:
- id: 22222222-2222-2222-2222-222222222222
name: Main XML feed
url: https://supplier.example/feed.xml
item_path: products/product
is_active: true
meta:
page: 1
limit: 25
total: 3
totalPages: 1
offset: 0
active_total: 2
mapped_total: 1
'500':
description: Unexpected list failure (v1Err).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
post:
tags:
- Feeds
summary: Create feed
description: |
Legacy body requires name + item_path (url optional). Dual-support also accepts
feed_type, sync_interval_minutes, sync_frequency (hours), and multipart file uploads.
requestBody:
content:
application/json:
schema:
type: object
required: [name, item_path]
properties:
name:
type: string
description: Display name for this input feed (required)
item_path:
type: string
description: |
XML item xpath / path stored in options.item_path (e.g. channel/item). Required.
url:
type: string
format: uri
nullable: true
description: |
HTTPS (or HTTP) URL of the remote feed. Nullable for upload-only feeds.
feed_type:
type: string
enum:
- xml
- csv
description: Source format. xml (default) or csv.
sync_interval_minutes:
type: integer
minimum: 1
default: 60
description: |
How often automatic sync should run, in minutes. Default 60. Minimum 1.
sync_frequency:
type: integer
minimum: 1
description: |
Legacy interval in hours. Converted to minutes when sync_interval_minutes is unset.
is_active:
type: boolean
description: |
When true, status is set to active after create; when false/omitted, typically unmapped.
example:
name: Nordic Webshop Google Merchant XML
item_path: channel/item
url: https://feeds.example.com/nordic/google-merchant.xml
feed_type: xml
sync_interval_minutes: 60
is_active: true
responses:
'201':
description: Feed created from JSON or multipart. HTTP 201 data envelope when Create succeeds.
is_active is honored for UUID ids.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedCreateResponse"
example:
data:
id: 22222222-2222-2222-2222-222222222222
name: Main XML feed
url: https://supplier.example/feed.xml
item_path: products/product
is_active: true
'400':
description: Invalid JSON/multipart, missing name/item_path (or name+url), upload failure, or
feeds.ClientError.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: "Missing required fields: name, item_path"
'500':
description: Unexpected create failure.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: could not create feed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/feeds/ {id}:
parameters:
- $ref: "#/components/parameters/ID"
get:
tags:
- Feeds
summary: Get feed
description: "Legacy envelope — { data: Feed }."
responses:
'200':
description: Single feed in data envelope when Feeds.Get succeeds for this company.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedGetResponse"
example:
data:
id: 22222222-2222-2222-2222-222222222222
name: Main XML feed
url: https://supplier.example/feed.xml
item_path: products/product
is_active: true
'400':
description: Path id is not a UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: Invalid id
'404':
description: Feed not found for this company.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: not_found
message: Not found
'500':
description: Unexpected get failure.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: get failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
patch:
tags:
- Feeds
summary: Update feed
description: "Returns flat PresentFeed JSON (same shape as data in GET /feeds/{id}, without the\
\ data wrapper)."
requestBody:
content:
application/json:
schema:
type: object
description: Partial update — only send fields to change
properties:
name:
type: string
description: Display name for this input feed
url:
type: string
format: uri
nullable: true
description: Remote feed URL, or null to clear
item_path:
type: string
description: XML item xpath / path (stored in options.item_path)
feed_type:
type: string
enum:
- xml
- csv
description: Source format. xml or csv.
status:
type: string
description: |
Feed lifecycle status (e.g. active, unmapped, error). Handler validates allowed values.
sync_interval_minutes:
type: integer
minimum: 1
description: Automatic sync interval in minutes. Minimum 1.
sync_frequency:
type: integer
minimum: 1
description: |
Legacy interval in hours. Converted to minutes when sync_interval_minutes is unset.
example:
name: Nordic Webshop Google Merchant XML (EU)
sync_interval_minutes: 120
status: active
responses:
'200':
description: Feed updated. Flat PresentFeed from shared handleUpdateFeed.
content:
application/json:
schema:
$ref: "#/components/schemas/PresentFeed"
example:
id: 22222222-2222-2222-2222-222222222222
name: Main XML feed
is_active: false
'400':
description: Invalid id/JSON or feeds.ClientError.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
delete:
tags:
- Feeds
summary: Delete feed
description: "Company-admin only. Flat { id, deleted: true } (not a legacy message envelope)."
responses:
'200':
description: Feed deleted. Flat FeedDeleted when handleDeleteFeed succeeds. Requires company-admin
capability (API keys pass as role api).
content:
application/json:
schema:
$ref: "#/components/schemas/FeedDeleted"
example:
id: 22222222-2222-2222-2222-222222222222
deleted: true
'400':
description: Invalid feed UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'403':
description: requireCompanyAdmin rejected the caller (session non-admin). API keys pass.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected delete failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: delete failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/feeds/{id}/sync:
post:
tags:
- Feeds
summary: Trigger feed sync
description: |
Legacy contract — HTTP 200 { data: { jobId } } (not 202). Enqueues a durable
feed_sync_jobs row and wakes the worker via NOTIFY; the API process does not
run sync work. Dual-support also returns job_id. Dashboard POST /api/feeds/{id}/sync
uses HTTP 202 with a flat job object — do not mix envelopes.
parameters:
- $ref: "#/components/parameters/ID"
responses:
"200":
description: Sync job durably enqueued. Returned when EnqueueSync succeeds. data.jobId (+ job_id alias).
content:
application/json:
schema:
$ref: "#/components/schemas/FeedSyncResponse"
example:
data:
jobId: 55555555-5555-5555-5555-555555555555
job_id: 55555555-5555-5555-5555-555555555555
'400':
description: Invalid id or feeds.ClientError (inactive feed, bad source, …).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: Invalid id
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: not_found
message: Feed not found
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid id
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: Invalid id
'500':
description: Failed to create sync job.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: Failed to create sync job
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'429':
description: Rate limit exceeded for heavy mutations (30/min/company). Retry-After 60.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: rate limit exceeded
/feeds/{id}/mappings:
parameters:
- $ref: "#/components/parameters/ID"
get:
tags:
- Feeds
summary: Get feed mappings
description: "Active feed_mappings row, or { mappings: [] } when none exist."
responses:
'200':
description: Active feed_mappings document for the feed, or an empty mappings payload when none
exist.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedMappings"
example:
mappings:
title: name
ean: gtin
'400':
description: Invalid feed UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected mappings read failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
put:
tags:
- Feeds
summary: Put feed mappings
description: Replace active mappings (bumps version).
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- mappings
properties:
mappings:
description: Mapping document (object or array)
example:
mappings:
item_path: channel/item
fields:
- source: g:id
target: product_id
- source: title
target: name
- source: g:gtin
target: gtin
- source: g:brand
target: brand
- source: g:image_link
target: main_image
responses:
'200':
description: Mappings replaced when PUT body validates and save succeeds.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedMappings"
example:
mappings:
title: name
ean: gtin
'400':
description: Invalid id/JSON or mapping validation error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/feeds/ {id}/extract-schema:
post:
tags:
- Feeds
summary: Extract feed schema sample paths and preview
description: Samples the feed source and returns discovered field paths plus a short preview.
parameters:
- $ref: "#/components/parameters/ID"
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
item_path:
type: string
description: Optional XML item path hint; inferred when omitted
example:
item_path: channel/item
responses:
'200':
description: Sample schema/fields extracted from the feed source.
content:
application/json:
schema:
$ref: "#/components/schemas/SchemaExtractResult"
example:
fields:
- name
- gtin
- price
item_path: products/product
'400':
description: Invalid id or extract client error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected extract failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: extract failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
"429": { $ref: "#/components/responses/TooManyRequests" }
/feeds/{id}/sync-process-sample:
post:
tags:
- Feeds
summary: Sync feed then process a sample of raw products
description: |
Optionally syncs the feed, then starts dashboard-style processing for up to N raw products
(default 10, max 100). Flat JSON; HTTP 202 when a job is queued, 200 when sync finishes with no products.
parameters:
- $ref: "#/components/parameters/ID"
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
limit:
type: integer
minimum: 1
maximum: 100
default: 10
description: |
Max raw products to queue after sync. Integer, default 10, minimum 1, maximum 100.
skip_sync:
type: boolean
default: false
description: |
When true, skip the feed sync step and process existing raw products only.
processing_type:
type: string
default: full
description: |
Pipeline mode — full (default) or a single step name (category, title, description, attributes).
example:
limit: 10
skip_sync: false
processing_type: full
responses:
'200':
description: Sample sync finished but produced no raw products to process (sync completed empty).
content:
application/json:
schema:
type: object
example:
ok: true
imported: 0
message: no products
'202':
description: Sample sync imported products and queued processing (async accept).
content:
application/json:
schema:
type: object
example:
accepted: true
job_id: 55555555-5555-5555-5555-555555555555
imported: 5
'400':
description: Invalid id or sample validation error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected sample failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: sample failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
"429": { $ref: "#/components/responses/TooManyRequests" }
/export-feeds:
get:
tags:
- Export feeds
summary: List export feeds
description: |
Legacy envelope — { data: ExportFeed[], meta: { page, limit, total } }.
Accepts page+limit (legacy defaults page=1, limit=25, max 100) or limit+offset.
parameters:
- name: page
in: query
schema:
type: integer
default: 1
minimum: 1
description: |
1-based page index for legacy page+limit mode. Integer, default 1, minimum 1.
- $ref: "#/components/parameters/LegacyLimit"
- $ref: "#/components/parameters/Offset"
responses:
'200':
description: "Paged export feeds as data[] + meta (presentV1ExportFeed rows)."
content:
application/json:
schema:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
$ref: "#/components/schemas/ExportFeedDetail"
meta:
type: object
example:
data:
- id: 66666666-6666-6666-6666-666666666666
name: Google Shopping
format: xml
is_active: true
meta:
page: 1
limit: 25
total: 1
'500':
description: Unexpected list failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
post:
tags:
- Export feeds
summary: Create export feed
description: "Legacy envelope — { data: ExportFeed }."
requestBody:
content:
application/json:
schema:
type: object
required:
- name
- format
properties:
name:
type: string
description: Display name for the export feed (required)
format:
type: string
enum:
- xml
- csv
description: Output format. xml or csv (required).
source_feed_id:
type: string
format: uuid
description: Optional input feed UUID this export is derived from
template:
type: object
description: |
Export template document (root/item/mappings). Structure depends on format.
structure:
type: object
description: Legacy alias for template — same object shape
mappings:
type: object
description: Field mapping object (source → target). May also live under template.
filters:
type: object
description: |
Product filters applied at generate time (e.g. statuses list). Object map.
root_xpath:
type: string
description: Optional XML root xpath hint for template builders
item_xpath:
type: string
description: Optional XML item xpath hint for template builders
example:
name: Warehouse Inventory CSV
format: csv
source_feed_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
filters:
statuses:
- completed
mappings:
title: processed_name
gtin: gtin
responses:
'201':
description: Export feed created when name+format validate. HTTP 201 data envelope.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/ExportFeedDetail"
example:
data:
id: 66666666-6666-6666-6666-666666666666
name: Google Shopping
format: xml
'400':
description: "Invalid JSON, missing name/format, or feeds.ClientError. Note: ClientOrLog fallback\
\ also uses flat Error on some paths."
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: "Missing required fields: name, format"
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/export-feeds/{id}:
parameters:
- $ref: "#/components/parameters/ID"
get:
tags:
- Export feeds
summary: Get export feed
description: Flat export feed row including template/filters (not the list presentV1ExportFeed /
data envelope).
responses:
'200':
description: Export feed detail when found.
content:
application/json:
schema:
$ref: "#/components/schemas/ExportFeedDetail"
example:
id: 66666666-6666-6666-6666-666666666666
name: Google Shopping
format: xml
'400':
description: Invalid UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected get failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: get failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
patch:
tags:
- Export feeds
summary: Update export feed
description: Flat updated export feed row.
requestBody:
content:
application/json:
schema:
type: object
description: Partial update — only send fields to change
properties:
name:
type: string
description: Display name for the export feed
is_active:
type: boolean
description: When false, public token URLs may still exist but feed is inactive
template:
type: object
description: Export template document (root/item/mappings)
filters:
type: object
description: Product filters applied at generate time
example:
name: Google Shopping XML EU
is_active: true
filters:
statuses:
- completed
responses:
'200':
description: Export feed updated.
content:
application/json:
schema:
$ref: "#/components/schemas/ExportFeedDetail"
example:
id: 66666666-6666-6666-6666-666666666666
name: Google Shopping EU
format: xml
'400':
description: Invalid JSON or client error.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
delete:
tags:
- Export feeds
summary: Delete export feed
description: "Company-admin only. Flat { status: ok } (not LegacySuccessMessage)."
responses:
'200':
description: Export feed deleted (admin capability; API keys pass). Flat deleted marker.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedDeleted"
example:
id: 66666666-6666-6666-6666-666666666666
deleted: true
'400':
description: Invalid UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'403':
description: requireCompanyAdmin rejected session non-admin.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected delete failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: delete failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/export-feeds/{id}/template:
put:
tags:
- Export feeds
summary: Update export feed template
description: Updates template and/or filters; returns flat ExportFeedDetail.
parameters:
- $ref: "#/components/parameters/ID"
requestBody:
content:
application/json:
schema:
type: object
properties:
template:
type: object
description: |
Full export template document (root, item path, field mappings). Replaces prior template when set.
filters:
type: object
description: |
Product filters for generation (e.g. statuses). Replaces prior filters when set.
example:
template:
root: rss
item: channel/item
mappings:
title: processed_name
gtin: gtin
filters:
statuses:
- completed
responses:
'200':
description: Template saved for the export feed.
content:
application/json:
schema:
$ref: "#/components/schemas/ExportFeedDetail"
example:
id: 66666666-6666-6666-6666-666666666666
name: Google Shopping
format: xml
'400':
description: Invalid id/body or template validation.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/export-feeds/{id}/rotate-token:
post:
tags:
- Export feeds
summary: Rotate public export URL token
description: |
Replaces public_token so the previous /api/public/export-feeds/{token}.{xml|csv}
URL stops working immediately. Requires company admin (or platform staff).
Response is a flat ExportFeedDetail including the new public_token.
parameters:
- $ref: "#/components/parameters/ID"
responses:
'200':
description: Token rotated; body includes the new public_token and URLs.
content:
application/json:
schema:
$ref: "#/components/schemas/ExportFeedDetail"
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
'403':
description: Admin required.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
"400": { $ref: "#/components/responses/BadRequest" }
"422": { $ref: "#/components/responses/ValidationError" }
"500": { $ref: "#/components/responses/InternalServerError" }
"429": { $ref: "#/components/responses/TooManyRequests" }
/export-feeds/{id}/generate:
post:
tags:
- Export feeds
summary: Generate export feed file
description: |
Legacy envelope — { data: { generated, format, filePath, downloadUrl, ... } }.
Content is streamed live via public download URL (not persisted to disk).
parameters:
- $ref: "#/components/parameters/ID"
responses:
'200':
description: Generation finished. data includes generated, format, downloadUrl, products_exported,
status. filePath may be null — documented under examples.value for RapiDoc safety.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: object
examples:
ok:
summary: Generated
value:
data:
generated: true
format: xml
filePath: null
downloadUrl: /api/public/export-feeds/tok_abc.xml
products_exported: 1284
last_generated_at: '2026-08-04T12:00:00Z'
status: ready
'400':
description: Invalid id or generation_failed client error (empty template, …).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: generation_failed
message: template is empty
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: not_found
message: Export feed not found
'500':
description: Generation failed internally.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: generation_failed
message: Failed to generate export feed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
"429": { $ref: "#/components/responses/TooManyRequests" }
/export-feeds/{id}/export-products:
post:
tags:
- Export feeds
summary: Export selected processed products
description: |
Streams the rendered export for the given processed product UUIDs.
Response body is CSV or XML bytes (not JSON). Headers include Content-Disposition and X-Products-Exported.
parameters:
- $ref: "#/components/parameters/ID"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- product_ids
properties:
product_ids:
type: array
minItems: 1
items:
type: string
format: uuid
description: |
Processed product UUIDs to include in the streamed export. Non-empty array.
example:
product_ids:
- 2c5ea4c0-4067-4e44-8c5a-9a8b7c6d5e4f
- 550e8400-e29b-41d4-a716-446655440001
responses:
'200':
description: Selected products exported / file bytes produced for this export feed.
content:
application/json:
schema:
type: object
example:
exported: 25
skipped: 2
'400':
description: Invalid id/body or selection validation.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'404':
description: Export feed not found.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'500':
description: Unexpected export failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: export failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
"429": { $ref: "#/components/responses/TooManyRequests" }
/campaigns:
get:
tags:
- Campaigns
summary: List seasonal campaign presets (legacy alias)
description: |
Alias of GET /marketing/calendar. Returns legacy envelope
{ data: { year, presets, prepared } }. Not email campaigns.
parameters:
- name: year
in: query
schema:
type: integer
example: 2026
minimum: 2000
maximum: 2100
description: |
Calendar year for seasonal presets (e.g. 2026). Integer. When omitted, server uses the current year.
responses:
'200':
description: Legacy alias of marketing calendar. data envelope around calendar payload (handleV1ListCampaigns
→ v1OK).
content:
application/json:
schema:
$ref: "#/components/schemas/MarketingCalendar"
example:
data:
year: 2026
presets:
- id: back_to_school
'400':
description: Calendar query validation failed (v1MarketingCalendar → v1Err).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: invalid year
'500':
description: Unexpected calendar failure.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: internal_error
message: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
/campaigns/prepare:
post:
tags:
- Campaigns
summary: Prepare seasonal campaign export (legacy alias)
description: |
Alias of POST /marketing/calendar/prepare. Legacy envelope
{ data: { preset_id, name, export_feed_id, created, ... } }.
requestBody:
content:
application/json:
schema:
type: object
required:
- preset_id
properties:
preset_id:
type: string
enum:
- black_friday
- christmas
description: |
Seasonal preset id. black_friday or christmas (required).
year:
type: integer
minimum: 2000
maximum: 2100
description: |
Target calendar year for date windows. Integer. Defaults to current year when omitted.
format:
type: string
enum:
- csv
- xml
default: csv
description: Export feed format for the prepared campaign. csv (default) or xml.
force_new:
type: boolean
description: |
When true, create a new export feed even if one already exists for this preset/year.
example:
preset_id: black_friday
year: 2026
format: csv
force_new: false
responses:
'200':
description: Prepare reused existing campaign. data envelope (PreparedCampaignEnvelope).
content:
application/json:
schema:
$ref: "#/components/schemas/PreparedCampaignEnvelope"
example:
data:
preset_id: back_to_school
year: 2026
created: false
'201':
description: Prepare created a new campaign. data envelope.
content:
application/json:
schema:
$ref: "#/components/schemas/PreparedCampaignEnvelope"
example:
data:
preset_id: back_to_school
year: 2026
created: true
'400':
description: Invalid JSON or marketing.ClientError (v1Err). ClientOrLog may emit flat Error.
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: validation_error
message: invalid json
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/marketing/calendar:
get:
tags:
- Campaigns
summary: List seasonal campaign presets
description: Canonical path (flat JSON). Prefer /campaigns for legacy clients.
parameters:
- name: year
in: query
schema:
type: integer
example: 2026
minimum: 2000
maximum: 2100
description: |
Calendar year for seasonal presets (e.g. 2026). Integer. When omitted, server uses the current year.
responses:
'200':
description: Flat marketing calendar payload (year, presets, prepared) when query validates.
content:
application/json:
schema:
$ref: "#/components/schemas/MarketingCalendar"
example:
year: 2026
presets:
- id: back_to_school
label: Back to school
'400':
description: Invalid query (year/preset).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid date
'500':
description: Unexpected calendar failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
/marketing/calendar/prepare:
post:
tags:
- Campaigns
summary: Prepare seasonal campaign export
description: Canonical path (flat JSON). Prefer /campaigns/prepare for legacy clients.
requestBody:
content:
application/json:
schema:
type: object
required:
- preset_id
properties:
preset_id:
type: string
enum:
- black_friday
- christmas
description: |
Seasonal preset id. black_friday or christmas (required).
year:
type: integer
minimum: 2000
maximum: 2100
description: |
Target calendar year for date windows. Integer. Defaults to current year when omitted.
format:
type: string
enum:
- csv
- xml
description: Export feed format for the prepared campaign. csv or xml.
force_new:
type: boolean
description: |
When true, create a new export feed even if one already exists for this preset/year.
example:
preset_id: black_friday
year: 2026
format: csv
force_new: false
responses:
'200':
description: Prepare reused an existing campaign (Created=false). Flat PreparedCampaign JSON.
content:
application/json:
schema:
$ref: "#/components/schemas/PreparedCampaign"
example:
preset_id: back_to_school
year: 2026
created: false
'201':
description: Prepare created a new campaign (Created=true). Flat PreparedCampaign JSON.
content:
application/json:
schema:
$ref: "#/components/schemas/PreparedCampaign"
example:
preset_id: back_to_school
year: 2026
created: true
'400':
description: Invalid JSON or marketing.ClientError.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/process:
get:
tags:
- Processing
summary: List processing jobs (raw_product_ids surface)
description: |
Flat job list for the /process + raw_product_ids contract.
Separate from legacy GET /products/process/ {id}.
parameters:
- $ref: "#/components/parameters/Limit"
responses:
'200':
description: "Recent dashboard-style jobs. Flat object with jobs[] and limit (handleV1ListProcessJobs\
\ — not legacy data envelope)."
content:
application/json:
schema:
type: object
required:
- jobs
- limit
properties:
jobs:
type: array
items:
$ref: "#/components/schemas/ProcessingJob"
limit:
type: integer
example:
jobs:
- id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: processing
total_products: 25
processed_products: 8
limit: 50
'500':
description: Unexpected job list failure.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
post:
tags:
- Processing
summary: Start processing job by raw_product_ids
description: |
Enqueues AI processing for existing raw product UUIDs.
Same pipeline as dashboard POST /api/processing/jobs.
Flat JSON body/response (no data wrapper); HTTP 202.
Pass raw_products.id only (GET /products data[].raw_product_id), never PresentProduct.id.
Separate from legacy POST /products/process (items[].ean → 200 { data }).
Plan gates return HTTP 402 PlanGateError (not the legacy coded envelope).
requestBody:
$ref: "#/components/requestBodies/StartProcessByRawIDs"
responses:
'202':
description: "Job accepted (HTTP 202). Flat ProcessingJobStartResponse; may include jobs[] when\
\ auto-split."
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingJobStartResponse"
example:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: pending
total_products: 2
processing_type: full
'400':
description: Invalid JSON, invalid raw_product_id, empty ids, or processing.ClientError / LogAndError.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not start processing job
'402':
description: Plan gate blocked start. Special shape from handleStartProcessingJob (error string
+ code + upgrade_url) — not the legacy coded envelope.
content:
application/json:
schema:
$ref: "#/components/schemas/PlanGateError"
example:
error: Insufficient credits
code: insufficient_credits
upgrade_url: /pricing
"429": { $ref: "#/components/responses/TooManyRequests" }
'500':
description: River enqueue failed after job row create.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: enqueue failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/process/{id}:
get:
tags:
- Processing
summary: Processing job status (raw_product_ids surface)
description: |
Flat ProcessingJob JSON. Not the legacy { data: { process_id, items } } envelope.
When status is completed (finished), response is additively enriched with items[]
and total_items (same processed product projection as GET /products/process/{id}).
parameters:
- $ref: "#/components/parameters/ID"
responses:
'200':
description: "Flat ProcessingJob when GetJob succeeds for this company. Completed jobs include items[]."
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingJob"
examples:
processing:
summary: In progress
value:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: processing
total_products: 25
processed_products: 8
processing_type: full
current_step: title
completed:
summary: Finished with processed products
value:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: completed
total_products: 1
processed_products: 1
processing_type: full
items:
- ean: '0123456789012'
id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
status: processed
title: Acme Wireless Earbuds ANC Black
total_items: 1
'400':
description: Path id is not a UUID.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid id
'404':
description: Job not found for this company.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/process/ {id}/cancel:
post:
tags:
- Processing
summary: Cancel processing job
parameters:
- $ref: "#/components/parameters/ID"
responses:
'200':
description: Cancel/terminate acknowledged. Returns the updated job object when CancelJob succeeds.
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingJob"
example:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: cancelled
total_products: 25
'400':
description: Invalid id, or processing.ClientError / LogAndError (job not cancellable).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not cancel job
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Resource id not found for this API-key company (or wrong tenant).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/process/{id}/terminate:
post:
tags:
- Processing
summary: Terminate processing job (alias of cancel)
description: "Pixel/legacy naming alias of POST /process/{id}/cancel."
parameters:
- $ref: "#/components/parameters/ID"
responses:
'200':
description: Cancel/terminate acknowledged. Returns the updated job object when CancelJob succeeds.
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingJob"
example:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: cancelled
total_products: 25
'400':
description: Invalid id, or processing.ClientError / LogAndError (job not cancellable).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not cancel job
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'500':
description: Unexpected server error for this operation (handler internal_error / flat Error).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: internal error
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Resource id not found for this API-key company (or wrong tenant).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/process/{id}/retry:
post:
tags:
- Processing
summary: Retry a failed or cancelled processing job
parameters:
- $ref: "#/components/parameters/ID"
responses:
'202':
description: Retry accepted (HTTP 202). Flat job after RetryJob + enqueue.
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessingJob"
example:
id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
status: pending
total_products: 25
'400':
description: Invalid id or job not retryable (ClientError / LogAndError).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: could not retry job
"429": { $ref: "#/components/responses/TooManyRequests" }
'500':
description: Enqueue failed after retry.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: enqueue failed
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'403':
description: Insufficient role or wrong company. On API-key routes, cross-tenant ids usually
return 404 instead. requireCompanyAdmin returns flat 'admin required' for session non-admin
callers (API keys use role api and pass).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'404':
description: Resource id not found for this API-key company (or wrong tenant).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'422':
description: Validation failed. Current public v1 handlers emit these cases as HTTP 400 (flat
or coded). 422 is documented for clients that expect an explicit validation status; body matches
FlatAPIError or LegacyAPIError.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error()
value:
error: invalid json
legacy:
summary: v1Err coded
value:
error:
code: validation_error
message: invalid json
/team/ {userID}:
parameters:
- name: userID
in: path
required: true
schema:
type: string
format: uuid
description: Membership user id within the selected company
patch:
tags:
- Team
summary: Update team member role
description: |
Company admin or platform admin. Demoting the last active admin returns 409.
Requires session cookie + CSRF double-submit (X-CSRF-Token).
Dashboard surface under /api (not the public /api/v1 API-key base).
security:
- SessionCookie: []
CSRFHeader: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- role
properties:
role:
type: string
enum:
- admin
- member
description: Normalized to admin|member (case-insensitive input accepted)
example:
role: member
responses:
'200':
description: Role updated (or unchanged)
content:
application/json:
schema:
type: object
required:
- status
- role
- user_id
properties:
status:
type: string
example: ok
role:
type: string
enum:
- admin
- member
user_id:
type: string
format: uuid
example:
status: ok
role: member
user_id: 4f3c2b1a-0e9d-4c8b-7a6f-5e4d3c2b1a09
'400':
description: Invalid request for this route (HTTP 400).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'403':
description: Forbidden (not company/platform admin)
'404':
description: Resource not found (HTTP 404).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: not found
'409':
description: Cannot demote the last admin
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: cannot demote the last admin
'401':
description: Missing or invalid API key. RequireAPIKey returns this when Authorization Bearer
and X-API-Key are both absent/empty or AuthenticateAPIKey returns ErrInvalidAPIKey. Legacy
coded envelope (not a flat string).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
'422':
description: Validation failed. Dashboard routes may use this status; prefer reading the message.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: validation failed
'500':
description: Unexpected server error (HTTP 500).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
/admin/emails/set-password:
post:
tags:
- Admin
summary: Re-issue set-password invites
description: |
Platform admin only. Prefers durable invite reissue (ReissueSetPasswordInvite);
falls back to HMAC set-password tokens when the user has no active membership.
Skips synthetic …@legacy.local emails. Rate-limited per admin.
When SMTP is disabled and a single user_id is provided, the response may include
a one-time token for local/staging link copy (never logs email/token).
Dashboard surface under /api (not the public /api/v1 API-key base).
security:
- SessionCookie: []
CSRFHeader: []
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
user_id:
type: string
format: uuid
description: |
Target user UUID. When omitted, bulk-targets users needing a password (capped).
example:
user_id: 4f3c2b1a-0e9d-4c8b-7a6f-5e4d3c2b1a09
responses:
'200':
description: Issue/send summary
content:
application/json:
schema:
type: object
required:
- sent
- issued
- skipped
- smtp_enabled
- mode
properties:
sent:
type: integer
issued:
type: integer
skipped:
type: integer
skipped_synthetic:
type: integer
skipped_ineligible:
type: integer
skipped_rate_limited:
type: integer
skipped_send:
type: integer
smtp_enabled:
type: boolean
mode:
type: string
example: invite
token:
type: string
description: Present only for single-user reissue when SMTP is off
example:
sent: 1
issued: 1
skipped: 0
skipped_synthetic: 0
skipped_ineligible: 0
skipped_rate_limited: 0
skipped_send: 0
smtp_enabled: true
mode: invite
'401':
description: Unauthorized — missing session or privilege for this dashboard route.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: unauthorized
'429':
description: Rate limit exceeded for this admin action. May include Retry-After.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: Too many requests
'503':
description: Dependency unavailable (for example mailer down).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: mailer unavailable
'400':
description: Invalid request for this route (HTTP 400).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: invalid json
'403':
description: Forbidden for this route (HTTP 403).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: admin required
'409':
description: Conflict with current resource state.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: conflict
'422':
description: Validation failed. Dashboard routes may use this status; prefer reading the message.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: validation failed
'500':
description: Unexpected server error (HTTP 500).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: list failed
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: API key (dk_...)
description: |
Preferred scheme for /api/v1. Value is the raw company API key
(prefix dk_...), not a JWT. Example header:
Authorization: Bearer dk_your_key
Create keys in Settings -> API keys. RapiDoc Try-it: paste the key, or
use "Use my API key" when logged into the docs page. Full keys are never
published in this YAML.
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: |
Alternate scheme for /api/v1. Same company API key as BearerAuth, sent
as header X-API-Key: dk_your_key. When both Authorization Bearer and
X-API-Key are present, Bearer wins.
SessionCookie:
type: apiKey
in: cookie
name: descrybe_session
description: Dashboard session cookie only (SESSION_COOKIE_NAME; default descrybe_session). Not
valid for /api/v1 public routes.
CSRFHeader:
type: apiKey
in: header
name: X-CSRF-Token
description: Dashboard CSRF double-submit header (must match descrybe_csrf cookie). Not used by
/api/v1 API-key routes.
parameters:
ID:
in: path
name: id
required: true
schema:
type: string
format: uuid
description: |
Resource UUID for this path (product, attribute, feed, export feed, or processing job).
Format: UUID string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). Required.
Limit:
in: query
name: limit
schema:
type: integer
default: 50
maximum: 200
minimum: 1
description: |
Page size for non-legacy list endpoints. Integer, default 50, minimum 1, maximum 200.
LegacyLimit:
in: query
name: limit
schema:
type: integer
default: 25
maximum: 100
minimum: 1
description: |
Page size for legacy public list endpoints. Integer, default 25, minimum 1, maximum 100.
Page:
in: query
name: page
schema:
type: integer
default: 1
minimum: 1
description: |
1-based page index for offset/page pagination. Integer, default 1, minimum 1.
Offset:
in: query
name: offset
schema:
type: integer
default: 0
description: Offset pagination (ignored when cursor or after_id is set)
Cursor:
in: query
name: cursor
schema:
type: string
description: Opaque keyset cursor from next_cursor (preferred for deep pages)
AfterID:
in: query
name: after_id
schema:
type: string
format: uuid
description: Keyset bookmark by product id; cursor wins when both are set
requestBodies:
LegacyStartProcessByEAN:
required: true
description: |
Prefer items[].ean (legacy public). Same handler also accepts raw_product_ids
when items is omitted. Sending neither returns validation_error.
raw_product_ids must be raw_products.id values (see GET /products data[].raw_product_id).
Do not pass PresentProduct.id / processed_products.id.
content:
application/json:
schema:
type: object
properties:
items:
type: array
minItems: 1
description: Primary legacy body — required unless raw_product_ids is set
items:
type: object
required:
- ean
properties:
ean:
type: string
description: GTIN / EAN barcode digits (required). Typically 8-14 characters.
category_unique_id:
type: string
description: Optional category unique_id; forces categorization when set
title:
type: string
description: Optional product title seed for enrichment
description:
type: string
description: Optional long description seed for enrichment
specifications:
type: array
description: Optional key/value specification pairs for attribute hints
items:
type: object
properties:
key:
type: string
description: Specification attribute key
value:
type: string
description: Specification attribute value
search:
type: string
description: Optional search keywords passed into enrichment context
main_image:
type: string
format: uri
description: Primary product image URL
more_images:
description: Additional image URLs as a single string or string array
oneOf:
- type: string
- type: array
items:
type: string
image_url:
type: string
format: uri
description: Alternate primary image URL field (alias of main_image)
additional_image_urls:
description: Extra image URLs as a single string or string array
oneOf:
- type: string
- type: array
items:
type: string
image_link:
type: string
format: uri
description: Google-style primary image_link URL
additional_image_link:
description: Google-style additional image links (string or array)
oneOf:
- type: string
- type: array
items:
type: string
raw_product_ids:
type: array
items:
type: string
format: uuid
minItems: 1
description: |
Alternate body — existing raw_products.id UUIDs (items takes precedence).
Not PresentProduct.id. Use GET /products data[].raw_product_id.
processing_type:
description: |
full (default); legacy steps category|title|description|attributes (string or
array of those steps); dual-mode also accepts v2 dashboard types such as
normalize_only, enhance_only, attributes_only, eprel_only, categorize_only
(ParseV1ProcessingType). Alias processingType accepted when values match.
oneOf:
- type: string
enum:
- full
- category
- title
- description
- attributes
- normalize_only
- enhance
- enhance_only
- enhance-only
- attributes_only
- specs
- specifications
- eprel
- eprel_only
- categorize
- categorize_only
- categorize_enhance
- type: array
items:
type: string
enum:
- category
- title
- description
- attributes
processingType:
description: CamelCase alias of processing_type (must match if both set)
oneOf:
- type: string
- type: array
items:
type: string
processing_types:
type: array
items:
type: string
description: Dashboard fine-grained steps; used when processing_type omitted
examples:
by_ean:
summary: "Legacy items[].ean (preferred)"
value:
items:
- ean: '4548736132174'
title: WH-1000XM5 Sony WH-1000XM5 Black
category_unique_id: electronics
main_image: https://images.example.com/products/wh1000xm5-black.jpg
more_images:
- https://images.example.com/products/wh1000xm5-black-side.jpg
- ean: 0194252092942
title: Anker PowerLine III USB-C to USB-C 2m
processing_type: full
by_raw_ids:
summary: Alternate raw_product_ids on same path
value:
raw_product_ids:
- 2c5ea4c0-4067-4e44-8c5a-9a8b7c6d5e4f
processing_type: normalize_only
StartProcessByRawIDs:
required: true
description: |
Dashboard-style start body — existing raw product UUIDs only (no items[].ean).
Used by POST /process. Returns flat ProcessingJob JSON with HTTP 202.
IDs must be raw_products.id (GET /products data[].raw_product_id), never PresentProduct.id.
content:
application/json:
schema:
type: object
required:
- raw_product_ids
properties:
raw_product_ids:
type: array
items:
type: string
format: uuid
minItems: 1
description: |
raw_products.id UUIDs to enqueue. Non-empty array of UUID strings (required).
Do not pass processed product list id values.
processing_type:
type: string
description: |
full (default) or a legacy/dashboard step (category, title, description,
attributes, normalize_only, enhance_only, …).
default: full
example: full
processing_types:
type: array
items:
type: string
description: Optional fine-grained steps (dashboard); StartJob uses processing_type
example:
raw_product_ids:
- 2c5ea4c0-4067-4e44-8c5a-9a8b7c6d5e4f
- 550e8400-e29b-41d4-a716-446655440001
processing_type: full
CreateCategory:
required: true
description: |
Create a catalog category. Requires name and unique_id. parent_id is accepted as an
alias of parent_unique_id.
content:
application/json:
schema:
type: object
required:
- name
- unique_id
properties:
name:
type: string
description: Display name shown in the catalog UI (required)
unique_id:
type: string
description: |
Stable slug identifier (e.g. headphones). Lowercase letters, digits,
underscores/hyphens. Required and unique within the company.
parent_unique_id:
type: string
nullable: true
description: Parent category unique_id, or null/omit for a root category
parent_id:
type: string
nullable: true
description: Legacy alias of parent_unique_id
description:
type: string
nullable: true
description: Optional human-readable category description
example:
name: Headphones
unique_id: electronics_audio_headphones
parent_id: electronics_audio
description: Over-ear and in-ear headphones
CreateAttribute:
required: true
description: |
Create a catalog attribute and link it to a category. Requires name, attribute_key,
value_type, and category_unique_id.
content:
application/json:
schema:
type: object
required:
- name
- attribute_key
- value_type
- category_unique_id
properties:
name:
type: string
description: Human-readable attribute label (required)
attribute_key:
type: string
description: |
Stable machine key (e.g. battery_life_hours). Snake_case preferred. Required.
value_type:
type: string
enum:
- string
- number
- list
- multiselect
description: |
Value shape. string, number, list, or multiselect (required).
category_unique_id:
type: string
description: Category unique_id slug to attach this attribute to (required)
unit:
type: string
nullable: true
description: Optional unit label (e.g. W, cm)
example:
type: string
nullable: true
description: Sample value for docs/UI hints
required:
type: boolean
default: false
description: When true, products in this category should supply the attribute
parent_key:
type: string
nullable: true
description: Optional parent attribute_key for nested/grouped attributes
example:
name: Battery Life
attribute_key: battery_life_hours
value_type: number
category_unique_id: electronics_audio_headphones
unit: h
required: false
example: '65'
schemas:
HealthStatus:
type: object
required:
- status
- service
- maintenance
- read_only
properties:
status:
type: string
example: ok
description: ok for liveness; ready/not_ready on /readyz
service:
type: string
example: api
maintenance:
type: boolean
description: When true
API is in maintenance mode: null
read_only:
type: boolean
description: When true
mutating writes are rejected: null
hypercare:
type: boolean
description: When true, tenant hypercare report-missing CTA is shown (P1-17)
checks:
type: object
additionalProperties:
type: string
description: Present on /readyz (e.g. database ok|fail|unavailable)
error:
type: string
description: Present on /readyz when not ready (safe public message)
ProductListResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items:
$ref: "#/components/schemas/PresentProduct"
meta:
type: object
required:
- page
- limit
- total
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
totalPages:
type: integer
ProductQualityListResponse:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
format: uuid
product_id:
type: string
name:
type: string
quality_score:
type: integer
quality_grade:
type: string
quality_checks:
type: object
additionalProperties: true
meta:
type: object
required:
- page
- limit
- total
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
PresentProduct:
type: object
properties:
id:
type: string
format: uuid
description: |
processed_products.id for this list row. Do not send as raw_product_ids —
use raw_product_id instead.
product_id:
type: string
example: SONY-WH1000XM5-B
name:
type: string
nullable: true
category:
type: string
nullable: true
status:
type: string
example: completed
raw_product_id:
type: string
format: uuid
nullable: true
description: |
raw_products.id for dual-mode POST /products/process and POST /process bodies.
Prefer this over id when starting jobs by UUID.
feed_id:
type: string
format: uuid
nullable: true
quality_score:
type: integer
quality_grade:
type: string
example: C
created_at:
type: string
format: date-time
nullable: true
updated_at:
type: string
format: date-time
nullable: true
ProcessedProduct:
type: object
properties:
id:
type: string
format: uuid
product_id:
type: string
example: SONY-WH1000XM5-B
name:
type: string
processed_name:
type: string
category:
type: string
description:
type: string
processed_description:
type: string
status:
type: string
example: completed
raw_product_id:
type: string
format: uuid
feed_id:
type: string
format: uuid
gtin:
type: string
attributes:
type: object
additionalProperties: true
processed_attributes:
type: object
additionalProperties: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CategoryListResponse:
type: object
required:
- categories
- total
- limit
- offset
properties:
categories:
type: array
items:
type: object
total:
type: integer
limit:
type: integer
offset:
type: integer
LegacyPaginationMeta:
type: object
required:
- page
- limit
- total
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
totalPages:
type: integer
LegacyCategoriesResponse:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
$ref: "#/components/schemas/LegacyCategory"
meta:
$ref: "#/components/schemas/LegacyPaginationMeta"
LegacyCategory:
type: object
properties:
id:
type: string
format: uuid
unique_id:
type: string
name:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CategoryDetail:
type: object
description: "Flat category row from GET/PATCH /categories/ {id} (not a legacy data envelope)."
properties:
id:
type: string
format: uuid
name:
type: string
unique_id:
type: string
parent_unique_id:
type: string
nullable: true
path:
type: string
nullable: true
level:
type: integer
position:
type: integer
is_active:
type: boolean
description:
type: string
nullable: true
title_template:
nullable: true
description_template:
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
LegacyCategoryCreateResponse:
type: object
required:
- data
properties:
data:
type: object
required:
- id
- unique_id
- name
properties:
id:
type: string
format: uuid
unique_id:
type: string
name:
type: string
LegacyAttributesResponse:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
$ref: "#/components/schemas/LegacyAttribute"
meta:
$ref: "#/components/schemas/LegacyPaginationMeta"
LegacyAttribute:
type: object
properties:
id:
type: string
format: uuid
key:
type: string
name:
type: string
type:
type: string
unit:
type: string
nullable: true
required:
type: boolean
category_unique_id:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
AttributeDetail:
type: object
description: "Flat attribute row from PATCH /attributes/{id} (not a legacy data envelope)."
properties:
id:
type: string
format: uuid
attribute_key:
type: string
name:
type: string
value_type:
type: string
enum: [string, number, list, multiselect]
unit:
type: string
nullable: true
example:
type: string
nullable: true
parent_key:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
LegacyAttributeCreateResponse:
type: object
required:
- data
properties:
data:
type: object
properties:
id:
type: string
format: uuid
key:
type: string
name:
type: string
type:
type: string
unit:
type: string
nullable: true
category_unique_id:
type: string
required:
type: boolean
LegacySuccessMessage:
type: object
required:
- data
properties:
data:
type: object
required:
- message
properties:
message:
type: string
FeedListResponse:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
$ref: "#/components/schemas/PresentFeed"
meta:
type: object
required:
- page
- limit
- total
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
description: All matching feeds
offset:
type: integer
active_total:
type: integer
description: Feeds with status active (truly syncing)
mapped_total:
type: integer
description: Feeds with status mapped (fields saved, not activated)
FeedGetResponse:
type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/PresentFeed"
FeedCreateResponse:
type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/PresentFeed"
FeedSyncResponse:
type: object
required:
- data
properties:
data:
type: object
required:
- jobId
properties:
jobId:
type: string
format: uuid
job_id:
type: string
format: uuid
description: Dual-support snake_case alias
PresentFeed:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
url:
type: string
nullable: true
item_path:
type: string
is_active:
type: boolean
product_count:
type: integer
status:
type: string
last_synced:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
feed_type:
type: string
description: Dual-support v2 field
sync_interval_minutes:
type: integer
description: Dual-support v2 field
last_synced_at:
type: string
format: date-time
nullable: true
options:
type: object
additionalProperties: true
FeedMappings:
type: object
properties:
id:
type: string
format: uuid
description: Absent when no mappings row exists yet
version:
type: integer
mappings:
description: Field mapping document (object or array). Empty array when none saved.
oneOf:
- type: object
additionalProperties: true
- type: array
items:
type: object
additionalProperties: true
SchemaExtractResult:
type: object
required:
- feed_id
- format
- fields
- sample_rows
properties:
feed_id:
type: string
format: uuid
format:
type: string
enum:
- xml
- csv
suggested_item_path:
type: string
item_path:
type: string
fields:
type: array
items:
type: object
properties:
path:
type: string
field_name:
type: string
data_type:
type: string
sample_values:
type: array
items:
type: string
unique_values_count:
type: integer
suggested_target:
type: string
sample_rows:
type: integer
preview:
type: string
preview_truncated:
type: boolean
ExportFeedDetail:
type: object
description: Flat export_feeds row from GET/PATCH/PUT template handlers (not presentV1ExportFeed
/ data envelope).
properties:
id:
type: string
format: uuid
name:
type: string
source_feed_id:
type: string
format: uuid
nullable: true
format:
type: string
enum:
- xml
- csv
public_token:
type: string
template:
type: object
additionalProperties: true
nullable: true
filters:
type: object
additionalProperties: true
nullable: true
is_active:
type: boolean
last_generated_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
StatusOK:
type: object
required:
- status
properties:
status:
type: string
example: ok
FeedDeleted:
type: object
required:
- id
- deleted
properties:
id:
type: string
format: uuid
deleted:
type: boolean
PreparedCampaign:
type: object
properties:
preset_id:
type: string
enum:
- black_friday
- christmas
name:
type: string
start_date:
type: string
format: date
end_date:
type: string
format: date
year:
type: integer
export_feed_id:
type: string
format: uuid
export_feed_name:
type: string
created:
type: boolean
description: True only on newly prepared campaigns
PreparedCampaignEnvelope:
type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/PreparedCampaign"
MarketingCalendar:
type: object
required:
- year
- presets
- prepared
properties:
year:
type: integer
presets:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
start_date:
type: string
format: date
end_date:
type: string
format: date
year:
type: integer
prepared:
type: array
items:
$ref: "#/components/schemas/PreparedCampaign"
ProcessingJob:
type: object
properties:
id:
type: string
format: uuid
company_id:
type: string
format: uuid
status:
type: string
example: pending
total_products:
type: integer
processed_products:
type: integer
processing_type:
type: string
current_step:
type: string
step_progress:
type: array
items:
type: object
properties:
step:
type: string
status:
type: string
note:
type: string
error:
type: string
nullable: true
started_at:
type: string
format: date-time
nullable: true
completed_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
items:
type: array
description: Present when status is completed — processed product payload (additive)
items:
$ref: "#/components/schemas/LegacyProcessItem"
total_items:
type: integer
description: Present with items when status is completed
jobs:
type: array
items:
$ref: "#/components/schemas/ProcessingJob"
description: Present when StartJob auto-splits into multiple jobs
sibling_job_ids:
type: array
items:
type: string
format: uuid
job_count:
type: integer
total_products_queued:
type: integer
ProcessingJobStartResponse:
description: Single Job object, or Job plus split metadata (jobs, sibling_job_ids, …)
allOf:
- $ref: "#/components/schemas/ProcessingJob"
PlanGateError:
type: object
properties:
error:
type: string
code:
type: string
enum:
- insufficient_credits
- product_limit
- ai_requires_upgrade
- eprel_requires_upgrade
- plan_gate
upgrade_url:
type: string
example: /pricing
FlatAPIError:
type: object
required:
- error
properties:
error:
type: string
LegacyAPIError:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
example: validation_error
message:
type: string
requestId:
type: string
LegacyProcessStartEnvelope:
type: object
required:
- data
properties:
data:
type: object
required:
- process_id
- message
properties:
process_id:
type: string
format: uuid
message:
type: string
total_items:
type: integer
processed_items:
type: integer
job_count:
type: integer
description: Present when StartJob auto-splits
sibling_job_ids:
type: array
items:
type: string
format: uuid
total_products_queued:
type: integer
errors:
type: array
items:
type: string
description: Per-item upsert failures when some EANs still queued
LegacyProcessStatusEnvelope:
type: object
required:
- data
properties:
data:
type: object
required:
- status
- process_id
properties:
status:
type: string
description: Uppercase job status (COMPLETED, FAILED, PENDING, PROCESSING, …)
example: COMPLETED
process_id:
type: string
format: uuid
processing_type:
description: Echo of requested type (string or step array)
oneOf:
- type: string
- type: array
items:
type: string
items:
type: array
description: Present when status is COMPLETED
items:
$ref: "#/components/schemas/LegacyProcessItem"
total_items:
type: integer
processed_at:
type: string
format: date-time
message:
type: string
error:
type: string
description: Present when status is FAILED
created_at:
type: string
format: date-time
started_at:
type: string
format: date-time
nullable: true
LegacyProcessItem:
2026-08-16 16:57:36 +02:00
type: object
description: |
One COMPLETED legacy process line (A1 / public contract). Successful items
expose category as categories.unique_id, a plain-text description string
(never a JSON array; HTML stripped), SEO meta_title / meta_description,
optional eprel object or null, clean attributes, images, and dual-mode ids.
Product display name is title; additive name mirrors the same processed title
(dual-mode for scorecards / legacy clients that read name).
required:
- ean
properties:
2026-08-09 22:47:43 +02:00
ean:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
id:
2026-08-16 16:57:36 +02:00
type: string
format: uuid
description: |
Legacy field: processed_products.id when enrichment succeeded.
Do not treat as raw_products.id. Same value as processed_product_id.
2026-08-09 22:47:43 +02:00
processed_product_id:
2026-08-16 16:57:36 +02:00
type: string
format: uuid
description: |
Explicit alias of id (processed_products.id). Prefer this name in new
dual-mode clients; id remains for backward compatibility.
2026-08-09 22:47:43 +02:00
raw_product_id:
2026-08-16 16:57:36 +02:00
type: string
format: uuid
description: |
raw_products.id for this job line. Use with POST /process raw_product_ids
or dashboard catalog APIs. Present whenever the job product row exists.
2026-08-09 22:47:43 +02:00
category:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: |
categories.unique_id for the assigned category. Opaque string — may be
numeric (e.g. "28" or "50") or slug-like. Not a URL path and not a UUID.
2026-08-09 22:47:43 +02:00
category_name:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: Human-readable category display name (not the unique_id).
2026-08-09 22:47:43 +02:00
title:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: |
Product display name (processed title). Primary legacy field; same value
as name when present.
name:
type: string
nullable: true
description: |
Additive alias of title (same processed display name). Prefer title in
new clients; name remains for scorecards and legacy readers.
2026-08-09 22:47:43 +02:00
meta_title:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: |
SEO title. Filled from processing meta or synthesized from title / category
when empty so successful items are not left with null meta.
2026-08-09 22:47:43 +02:00
meta_description:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: |
SEO description (word-safe truncate). Distinct from body description when
possible; synthesized from plain description when DB meta is empty.
2026-08-09 22:47:43 +02:00
description:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
description: |
Plain-text product body description. Always a string — never a one-element
JSON array. Feed HTML tags are stripped; newlines may remain between paragraphs.
attributes:
type: object
additionalProperties: true
nullable: true
description: |
Characteristic catalog attributes only (brand, model, dims, warranty, …).
Core fields and eprel_* keys are not duplicated here.
2026-08-09 22:47:43 +02:00
main_image:
2026-08-16 16:57:36 +02:00
type: string
nullable: true
2026-08-09 22:47:43 +02:00
more_images:
2026-08-16 16:57:36 +02:00
type: array
items:
type: string
nullable: true
2026-08-09 22:47:43 +02:00
eprel:
2026-08-16 16:57:36 +02:00
nullable: true
type: object
description: |
EU energy label payload when EPREL data is available; otherwise null.
Prefer this object over raw eprel_* keys inside attributes.
Live shape keys: id, label, pdf, energy_class, energy_scale.
properties:
id:
type: string
description: EPREL product registration id (same as eprel_id).
2026-08-09 22:47:43 +02:00
label:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
pdf:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
energy_class:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
energy_scale:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
status:
2026-08-16 16:57:36 +02:00
type: string
description: Per-item outcome — processed on success; not_found / failed / cancelled otherwise
example: processed
2026-08-09 22:47:43 +02:00
error:
2026-08-16 16:57:36 +02:00
type: string
2026-08-09 22:47:43 +02:00
examples:
ProcessingJobAccepted:
summary: Single processing job accepted (POST /process)
value:
id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
company_id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
status: pending
total_products: 2
processed_products: 0
processing_type: full
current_step: category
step_progress:
- step: category
status: pending
- step: title
status: pending
- step: description
status: pending
- step: attributes
status: pending
created_at: '2026-08-04T09:59:50Z'
ProcessingJobSplitAccepted:
summary: Auto-split batch (POST /process)
value:
id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
company_id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
status: pending
total_products: 50
processed_products: 0
processing_type: full
current_step: category
created_at: '2026-08-04T09:59:50Z'
jobs:
- id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
status: pending
total_products: 50
- id: 1b4e28ba-2fa1-4d3a-9c6e-7f8a9b0c1d2e
status: pending
total_products: 50
sibling_job_ids:
- 1b4e28ba-2fa1-4d3a-9c6e-7f8a9b0c1d2e
job_count: 2
total_products_queued: 100
ProcessingJobRunning:
summary: "Processing job in progress (GET /process/ {id})"
value:
id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
company_id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
status: processing
total_products: 25
processed_products: 8
processing_type: full
current_step: title
step_progress:
- step: category
status: done
- step: title
status: running
- step: description
status: pending
- step: attributes
status: pending
started_at: '2026-08-04T10:00:00Z'
created_at: '2026-08-04T09:59:50Z'
LegacyProcessCompleted:
2026-08-16 16:57:36 +02:00
summary: "Completed legacy poll with A1-shaped item (unique_id category, plain description, meta, eprel)"
2026-08-09 22:47:43 +02:00
value:
data:
status: COMPLETED
process_id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
processing_type: full
items:
2026-08-16 16:57:36 +02:00
- ean: '8606019604493'
2026-08-09 22:47:43 +02:00
id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
processed_product_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
raw_product_id: cccccccc-cccc-cccc-cccc-cccccccccccc
status: processed
2026-08-16 16:57:36 +02:00
category: '50'
category_name: Cookers
title: VOX electric cooker EHT 6020 WG
name: VOX electric cooker EHT 6020 WG
meta_title: VOX electric cooker EHT 6020 WG | 50
meta_description: Affordable electric cooker with four plates and a 65 L fan oven.
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven. Energy class A with practical everyday capacity."
2026-08-09 22:47:43 +02:00
attributes:
2026-08-16 16:57:36 +02:00
brand: Vox
product_model: EHT6020WG
width: 0.6 m
height: 0.85 m
depth: 0.6 m
weight: 42.81 kg
warranty: 60 months
main_image: https://images.example.com/products/vox-eht6020wg.jpg
2026-08-09 22:47:43 +02:00
more_images:
2026-08-16 16:57:36 +02:00
- https://images.example.com/products/vox-eht6020wg-side.jpg
eprel:
id: "1234567"
label: https://eprel.ec.europa.eu/label/Example
pdf: https://eprel.ec.europa.eu/fiches/Example.pdf
energy_class: A
energy_scale: A-G
2026-08-09 22:47:43 +02:00
total_items: 1
processed_at: '2026-08-04T10:04:12Z'
LegacyProcessInProgress:
summary: In-progress legacy poll
value:
data:
status: PROCESSING
process_id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
processing_type: full
created_at: '2026-08-04T09:59:50Z'
started_at: '2026-08-04T10:00:00Z'
LegacyProcessFailed:
summary: Failed legacy poll
value:
data:
status: FAILED
process_id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
processing_type: full
error: Processing failed
responses:
Unauthorized:
description: Missing or invalid API key (RequireAPIKey / CodedError legacy envelope).
content:
application/json:
schema:
$ref: "#/components/schemas/LegacyAPIError"
example:
error:
code: unauthorized
message: Unauthorized
Forbidden:
description: Wrong company or insufficient role. Flat error string. Cross-tenant resource ids on
API-key routes usually return 404 instead.
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: forbidden
NotFound:
description: Resource not found for this API key company (missing id or wrong tenant).
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Dashboard-shared handlers
value:
error: not found
legacy:
summary: Legacy v1Err helpers
value:
error:
code: not_found
message: Not found
BadRequest:
description: Invalid request or client validation error (handlers use HTTP 400).
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error helper
value:
error: invalid json
legacy:
summary: Legacy v1Err validation
value:
error:
code: validation_error
message: '''items'' array is required'
ValidationError:
description: Validation error. Public v1 handlers return HTTP 400 for these cases (OpenAPI also
lists 422 for clients that expect that status).
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat validation
value:
error: invalid id
legacy:
summary: Legacy coded validation
value:
error:
code: validation_error
message: invalid id
Conflict:
description: Conflict (for example cannot demote or remove the last company admin).
content:
application/json:
schema:
$ref: "#/components/schemas/FlatAPIError"
example:
error: cannot demote the last admin
TooManyRequests:
description: "Rate limited (RateLimitV1Process heavy mutations and/or processing StartLimiter).\
\ May include Retry-After: 60."
headers:
Retry-After:
schema:
type: integer
description: Seconds until retry (set by RateLimitV1Process)
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Middleware limiter
value:
error: rate limit exceeded
legacy:
summary: Processing StartLimiter
value:
error:
code: rate_limited
message: rate limit exceeded
InternalServerError:
description: Unexpected server failure (auth backend, enqueue, list/get failures).
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/FlatAPIError"
- $ref: "#/components/schemas/LegacyAPIError"
examples:
flat:
summary: Flat Error helper
value:
error: list failed
legacy:
summary: Legacy coded internal error
value:
error:
code: internal_error
message: list failed
` )