6643 lines
232 KiB
Go
6643 lines
232 KiB
Go
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.
|
|
|
|
### 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
|
|
(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:
|
|
processed_product_id (same value as id), raw_product_id (raw_products.id), and
|
|
name (same value as title) for dual-mode clients / scorecards.
|
|
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:
|
|
summary: Completed A1-shaped item
|
|
value:
|
|
data:
|
|
status: COMPLETED
|
|
process_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
|
processing_type: full
|
|
items:
|
|
- ean: '8606019604493'
|
|
id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
|
|
processed_product_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
|
|
raw_product_id: cccccccc-cccc-cccc-cccc-cccccccccccc
|
|
status: processed
|
|
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
|
|
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:
|
|
type: object
|
|
description: |
|
|
One COMPLETED legacy process line (A1 / public contract). Successful items
|
|
expose category as categories.unique_id, a description string that may
|
|
include category formula HTML (h1/h2/h3/h4, p, ul — never a JSON array),
|
|
SEO meta_title / meta_description (plain text), 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:
|
|
ean:
|
|
type: string
|
|
id:
|
|
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.
|
|
processed_product_id:
|
|
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.
|
|
raw_product_id:
|
|
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.
|
|
category:
|
|
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.
|
|
category_name:
|
|
type: string
|
|
nullable: true
|
|
description: Human-readable category display name (not the unique_id).
|
|
title:
|
|
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.
|
|
meta_title:
|
|
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.
|
|
meta_description:
|
|
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.
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Product body description as a string — never a one-element JSON array.
|
|
When the category description_template requires multi-section markup,
|
|
this field retains formula HTML tags (h1/h2/h3/h4, p, ul). Feed-only
|
|
plain text remains plain; meta_description stays plain SEO text.
|
|
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.
|
|
main_image:
|
|
type: string
|
|
nullable: true
|
|
more_images:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
eprel:
|
|
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).
|
|
label:
|
|
type: string
|
|
pdf:
|
|
type: string
|
|
energy_class:
|
|
type: string
|
|
energy_scale:
|
|
type: string
|
|
status:
|
|
type: string
|
|
description: Per-item outcome — processed on success; not_found / failed / cancelled otherwise
|
|
example: processed
|
|
error:
|
|
type: string
|
|
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:
|
|
summary: "Completed legacy poll with A1-shaped item (unique_id category, plain description, meta, eprel)"
|
|
value:
|
|
data:
|
|
status: COMPLETED
|
|
process_id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
|
|
processing_type: full
|
|
items:
|
|
- ean: '8606019604493'
|
|
id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
|
|
processed_product_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
|
|
raw_product_id: cccccccc-cccc-cccc-cccc-cccccccccccc
|
|
status: processed
|
|
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."
|
|
attributes:
|
|
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
|
|
more_images:
|
|
- 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
|
|
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
|
|
`)
|