6712 lines
239 KiB
Go
6712 lines
239 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 (Bearer or X-API-Key) except
|
|
GET /health and GET /openapi.yaml.
|
|
|
|
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 key-type error.
|
|
|
|
### 401 Unauthorized
|
|
|
|
Missing, empty, unknown, revoked, or non-migrated (pre-cutover) keys return
|
|
HTTP 401 from RequireAPIKey with the coded envelope:
|
|
|
|
{ "error": { "code": "unauthorized", "message": "Unauthorized" } }
|
|
|
|
See components.responses.Unauthorized (schema CodedAPIError). 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. **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 POST /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[] omit internal UUIDs (id /
|
|
processed_product_id / raw_product_id). Use GET /products when a UUID is needed.
|
|
Display name is items[].name (title omitted when identical).
|
|
|
|
### AI descriptions (HTML body, not a short blurb)
|
|
|
|
Default processing_type full runs normalize, parse specs, fill fields, EPREL
|
|
(when enabled), categorize, then AI enhance. Enhance writes the display name
|
|
and a full product body.
|
|
|
|
Completed items[].description is that body: one HTML string (tags matching
|
|
section types h1/h2/h3/h4, p, ul) — never a JSON array and never a one- or
|
|
two-sentence slogan. Length and structure come from the assigned category
|
|
description_template (description formula): ordered sections with their own
|
|
instructions. A formula can ask for a short paragraph, 100-word blocks,
|
|
several heading/list sections, or a very large multi-section article. There
|
|
is no request-body size enum; configure length on the category. When a
|
|
category has no formula, the default is still 1-3 factual paragraphs with
|
|
limited HTML, not a headline-only stub.
|
|
|
|
items[].description on the start body is optional source text the model may
|
|
rewrite. Poll GET /products/process/{id} for the generated HTML.
|
|
meta_title / meta_description are separate short SEO fields and may be
|
|
omitted depending on company configuration.
|
|
|
|
Note: Dashboard JSON under /api/* uses session cookies + CSRF and is separate
|
|
from this public API-key surface. Other 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 (/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: |
|
|
Public product list. Envelope is { data, meta } (not flat products/offset).
|
|
Query params: 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/PublicLimit"
|
|
- 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
|
|
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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: invalid feed id
|
|
'500':
|
|
description: Unexpected database/list failure in handleV1ListProducts.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: unauthorized
|
|
message: Unauthorized
|
|
/products/quality:
|
|
get:
|
|
tags:
|
|
- Products
|
|
summary: List product quality scores
|
|
description: |
|
|
Quality listing with { data, meta }. Defaults status=completed.
|
|
Optional min_score filters rows after scoring.
|
|
parameters:
|
|
- $ref: "#/components/parameters/Page"
|
|
- $ref: "#/components/parameters/PublicLimit"
|
|
- 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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: invalid min_score
|
|
'500':
|
|
description: Unexpected list failure in handleV1ListProductQuality.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: |
|
|
Public GET returns a data envelope without mapped_data, has_* flags, or processed_* internals.
|
|
Public fields match process items: name, description, attributes, images, eprel.
|
|
List endpoints use data plus meta.
|
|
responses:
|
|
'200':
|
|
description: "Single processed product as a data envelope (no mapped_data)."
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/PresentProduct"
|
|
example:
|
|
data:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
patch:
|
|
tags:
|
|
- Products
|
|
summary: Update processed product
|
|
description: "Flat ProcessedProduct JSON (not a { 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
|
|
processed_description:
|
|
type: string
|
|
description: |
|
|
Enriched product body as one HTML string from the category
|
|
description formula. Length follows that formula (including very
|
|
large multi-section copy), not a short summary.
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/products/process:
|
|
post:
|
|
tags:
|
|
- Processing
|
|
summary: Start processing by EAN
|
|
description: |
|
|
Public process API. Upserts raw products from items[].ean (GTIN), enqueues
|
|
one processing job, returns HTTP 200 with a data envelope.
|
|
|
|
Default processing_type full runs the full pipeline including AI enhance:
|
|
the model writes a retail title (name) and a full HTML product description
|
|
driven by the category description formula. That body can be long —
|
|
multiple headings, paragraphs, and lists, including very large
|
|
multi-section articles — not a one- or two-sentence summary.
|
|
|
|
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 coded envelope).
|
|
|
|
Poll GET /products/process/{id} until status is COMPLETED, then read
|
|
items[].description for the generated HTML.
|
|
|
|
Not an alias of POST /process (flat ProcessingJob / 202). Do not mix envelopes.
|
|
requestBody:
|
|
$ref: "#/components/requestBodies/StartProcessByEAN"
|
|
responses:
|
|
'200':
|
|
description: "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/ProcessStartEnvelope"
|
|
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/CodedAPIError"
|
|
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 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/CodedAPIError"
|
|
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 job.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/products/process/{id}:
|
|
get:
|
|
tags:
|
|
- Processing
|
|
summary: Processing job status by process_id
|
|
description: |
|
|
Poll 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, name is the product display name (title is omitted when
|
|
identical). description is the generated product body: one HTML string
|
|
following the category description formula (h1/h2/h3/h4, p, ul). Expect
|
|
real catalog copy — often several paragraphs and lists, sometimes a very
|
|
large article — not a one- or two-sentence blurb. Internal UUIDs (id /
|
|
processed_product_id / raw_product_id) are omitted — use GET /products for
|
|
those. Optional meta_title and meta_description are short SEO fields and
|
|
may be omitted depending on company configuration.
|
|
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: "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/ProcessStatusEnvelope"
|
|
examples:
|
|
completed:
|
|
summary: Completed item
|
|
value:
|
|
data:
|
|
status: COMPLETED
|
|
process_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
|
processing_type: full
|
|
items:
|
|
- ean: '8606019604493'
|
|
status: processed
|
|
category: Cookers
|
|
category_id: '50'
|
|
category_name: Cookers
|
|
name: VOX electric cooker EHT 6020 WG
|
|
description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
|
|
attributes:
|
|
brand: Vox
|
|
product_model: EHT6020WG
|
|
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'
|
|
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/CodedAPIError"
|
|
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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: not_found
|
|
message: Processing job not found
|
|
'500':
|
|
description: Unexpected failure loading job status.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/categories:
|
|
get:
|
|
tags:
|
|
- Categories
|
|
summary: List categories
|
|
description: "Paged list — { 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/CategoriesEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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/CategoryCreateEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/categories/create:
|
|
post:
|
|
tags:
|
|
- Categories
|
|
summary: Create category (alias of POST /categories)
|
|
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/CategoryCreateEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: |
|
|
Public GET returns a data envelope of EnvelopeCategory (no prompts or formulas). Path id must be the category UUID.
|
|
List endpoints use data plus meta; this GET omits meta.
|
|
responses:
|
|
'200':
|
|
description: Category detail as a data envelope of EnvelopeCategory when found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/EnvelopeCategory"
|
|
example:
|
|
data:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 { 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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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.
|
|
responses:
|
|
'200':
|
|
description: Category deleted by unique_id path param. Returned when DeleteCategoryByUniqueID
|
|
succeeds. Envelope data.message.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/attributes:
|
|
get:
|
|
tags:
|
|
- Attributes
|
|
summary: List attributes
|
|
description: "Paged list — { 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/AttributesEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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/AttributeCreateEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/attributes/create:
|
|
post:
|
|
tags:
|
|
- Attributes
|
|
summary: Create attribute (alias of POST /attributes)
|
|
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/AttributeCreateEnvelope"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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/SuccessMessage"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/feeds:
|
|
get:
|
|
tags:
|
|
- Feeds
|
|
summary: List input feeds
|
|
description: |
|
|
Paged list — { data: Feed[], meta: { page, limit, total } }.
|
|
Accepts page+limit (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 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/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: unauthorized
|
|
message: Unauthorized
|
|
post:
|
|
tags:
|
|
- Feeds
|
|
summary: Create feed
|
|
description: |
|
|
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: |
|
|
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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: "Missing required fields: name, item_path"
|
|
'500':
|
|
description: Unexpected create failure.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: "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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: Invalid id
|
|
'404':
|
|
description: Feed not found for this company.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: not_found
|
|
message: Not found
|
|
'500':
|
|
description: Unexpected get failure.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: |
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: v1Err coded
|
|
value:
|
|
error:
|
|
code: validation_error
|
|
message: invalid json
|
|
/feeds/{id}/sync:
|
|
post:
|
|
tags:
|
|
- Feeds
|
|
summary: Trigger feed sync
|
|
description: |
|
|
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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: Invalid id
|
|
'404':
|
|
description: Feed not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid id
|
|
coded:
|
|
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/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: |
|
|
Envelope — { data: ExportFeed[], meta: { page, limit, total } }.
|
|
Accepts page+limit (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 page+limit mode. Integer, default 1, minimum 1.
|
|
- $ref: "#/components/parameters/PublicLimit"
|
|
- $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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: unauthorized
|
|
message: Unauthorized
|
|
post:
|
|
tags:
|
|
- Export feeds
|
|
summary: Create export feed
|
|
description: "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: 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/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 SuccessMessage)."
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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/CodedAPIError"
|
|
'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: |
|
|
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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: generation_failed
|
|
message: template is empty
|
|
'404':
|
|
description: Export feed not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: not_found
|
|
message: Export feed not found
|
|
'500':
|
|
description: Generation failed internally.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 (alias of GET /marketing/calendar)
|
|
description: |
|
|
Alias of GET /marketing/calendar. Returns the same 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: 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/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: validation_error
|
|
message: invalid year
|
|
'500':
|
|
description: Unexpected calendar failure.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: unauthorized
|
|
message: Unauthorized
|
|
/campaigns/prepare:
|
|
post:
|
|
tags:
|
|
- Campaigns
|
|
summary: Prepare seasonal campaign export (alias of POST /marketing/calendar/prepare)
|
|
description: |
|
|
Alias of POST /marketing/calendar/prepare. 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/CodedAPIError"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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). /campaigns is an alias of this path.
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
example:
|
|
error:
|
|
code: unauthorized
|
|
message: Unauthorized
|
|
/marketing/calendar/prepare:
|
|
post:
|
|
tags:
|
|
- Campaigns
|
|
summary: Prepare seasonal campaign export
|
|
description: Canonical path (flat JSON). /campaigns/prepare is an alias of this path.
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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 GET /products/process/{id}.
|
|
parameters:
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
'200':
|
|
description: "Recent dashboard-style jobs. Flat object with jobs[] and limit (handleV1ListProcessJobs\
|
|
\ — not a 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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 POST /products/process (items[].ean → 200 { data }).
|
|
Plan gates return HTTP 402 PlanGateError (not the 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 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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: |
|
|
Public processing job JSON: status, counts, timestamps, processing_type.
|
|
Omits current_step, step_progress, and company_id. Completed jobs still include
|
|
items[] with the same AI HTML description contract as GET /products/process/{id}
|
|
(category formula body, not a one- or two-sentence blurb).
|
|
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
|
|
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'
|
|
status: processed
|
|
name: Acme Wireless Earbuds ANC Black
|
|
category: Headphones
|
|
description: "<h1>Acme Wireless Earbuds ANC Black</h1><p>Acme Wireless Earbuds ANC Black are in-ear headphones for commuting and desk work. Active noise cancellation, a secure fit, and a charging case cover a full workday of listening. The compact buds sit flush enough for glasses and hats, and the case fits a jacket pocket.</p><p>Pair over Bluetooth and switch between a phone and a laptop with the onboard controls. Transparency mode lets nearby voices through without removing the buds. The default ear-tip set covers common sizes; swap tips if bass feels weak or the seal slips during a walk.</p><h2>In the box</h2><ul><li>Wireless earbuds (left and right)</li><li>Charging case with USB-C cable</li><li>Three ear-tip sizes</li><li>Quick-start guide</li></ul><p>Charge the case before first use. Keep the charging contacts dry and store the buds in the case so they sleep and recharge between sessions. For replacement tips or a spare case, quote the product name and the EAN on the box.</p>"
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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: "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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 CodedAPIError.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/FlatAPIError"
|
|
- $ref: "#/components/schemas/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error()
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
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. Coded
|
|
envelope (not a flat string).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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 placeholder 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 other list endpoints. Integer, default 50, minimum 1, maximum 200.
|
|
PublicLimit:
|
|
in: query
|
|
name: limit
|
|
schema:
|
|
type: integer
|
|
default: 25
|
|
maximum: 100
|
|
minimum: 1
|
|
description: |
|
|
Page size for 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:
|
|
StartProcessByEAN:
|
|
required: true
|
|
description: |
|
|
Prefer items[].ean. 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 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 source/seed text. AI enhance rewrites this into the
|
|
category description-formula HTML body. It is not the final
|
|
poll payload and does not cap length to a short summary.
|
|
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) runs the whole pipeline including AI enhance, which
|
|
writes formula-driven HTML descriptions (not a one- or two-sentence
|
|
blurb). Named 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.
|
|
The description step still emits the category formula HTML body.
|
|
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: "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 named 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: 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
|
|
description: |
|
|
Product body. After processing this is the AI HTML description
|
|
(category formula), not a one- or two-sentence summary.
|
|
processed_description:
|
|
type: string
|
|
description: |
|
|
Enriched HTML body from AI enhance. Same contract as ProcessItem.description.
|
|
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
|
|
EnvelopePaginationMeta:
|
|
type: object
|
|
required:
|
|
- page
|
|
- limit
|
|
- total
|
|
properties:
|
|
page:
|
|
type: integer
|
|
limit:
|
|
type: integer
|
|
total:
|
|
type: integer
|
|
totalPages:
|
|
type: integer
|
|
CategoriesEnvelope:
|
|
type: object
|
|
required:
|
|
- data
|
|
- meta
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/EnvelopeCategory"
|
|
meta:
|
|
$ref: "#/components/schemas/EnvelopePaginationMeta"
|
|
EnvelopeCategory:
|
|
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 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: |
|
|
Title formula for AI enhance. Controls how processed name is built
|
|
(type, brand, model, and other slots).
|
|
description_template:
|
|
nullable: true
|
|
description: |
|
|
Description formula for AI enhance. Ordered sections (h1/h2/h3/h4, p, ul)
|
|
with per-section instructions. Controls HTML structure and length of
|
|
processed items[].description — short, long, or very large copy.
|
|
Not a one- or two-sentence cap.
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
CategoryCreateEnvelope:
|
|
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
|
|
AttributesEnvelope:
|
|
type: object
|
|
required:
|
|
- data
|
|
- meta
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/EnvelopeAttribute"
|
|
meta:
|
|
$ref: "#/components/schemas/EnvelopePaginationMeta"
|
|
EnvelopeAttribute:
|
|
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 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
|
|
AttributeCreateEnvelope:
|
|
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
|
|
SuccessMessage:
|
|
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/ProcessItem"
|
|
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
|
|
CodedAPIError:
|
|
type: object
|
|
required:
|
|
- error
|
|
properties:
|
|
error:
|
|
type: object
|
|
required:
|
|
- code
|
|
- message
|
|
properties:
|
|
code:
|
|
type: string
|
|
example: validation_error
|
|
message:
|
|
type: string
|
|
requestId:
|
|
type: string
|
|
ProcessStartEnvelope:
|
|
type: object
|
|
required:
|
|
- data
|
|
properties:
|
|
data:
|
|
type: object
|
|
required:
|
|
- process_id
|
|
- message
|
|
properties:
|
|
process_id:
|
|
type: string
|
|
format: uuid
|
|
status:
|
|
type: string
|
|
description: |
|
|
Job lifecycle status on enqueue. Always pending (or processing if the
|
|
worker already picked it up). Never COMPLETED on start — poll GET
|
|
/products/process/{id} for completion.
|
|
example: pending
|
|
message:
|
|
type: string
|
|
total_items:
|
|
type: integer
|
|
description: Number of products accepted into the job (enqueue size).
|
|
processed_items:
|
|
type: integer
|
|
description: |
|
|
Count of items that finished processing. Always 0 on start while the
|
|
job is pending/processing; increments as the worker completes products.
|
|
example: 0
|
|
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
|
|
ProcessStatusEnvelope:
|
|
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/ProcessItem"
|
|
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
|
|
ProcessItem:
|
|
type: object
|
|
description: |
|
|
One COMPLETED process line (public contract). Successful items
|
|
expose category as the human-readable display name (category_id holds
|
|
categories.unique_id), a description string that is generated HTML
|
|
product copy (h1/h2/h3/h4, p, ul — never a JSON array and never a one-
|
|
or two-sentence slogan), optional SEO meta_title / meta_description
|
|
(plain text; some companies omit these fields), optional eprel object
|
|
or null, clean attributes, and images.
|
|
Product display name is name (primary). title is omitted when identical to name.
|
|
Internal UUIDs (id, processed_product_id, raw_product_id) are omitted from this
|
|
public shape — use catalog APIs when a product UUID is required.
|
|
Property order prefers human-readable fields first (ean, name, category,
|
|
description, attributes, images, eprel).
|
|
required:
|
|
- ean
|
|
properties:
|
|
ean:
|
|
type: string
|
|
category:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Human-readable category display name (same value as category_name when
|
|
resolved). Prefer this for UI. Machine unique_id is category_id.
|
|
category_id:
|
|
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 (mirrors category when both are set).
|
|
name:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Product display name (processed title). Primary field for clients.
|
|
title:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Optional alias of name. Omitted when identical to name.
|
|
meta_title:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Optional SEO title. When present, filled from processing meta or
|
|
synthesized from name / category. Some companies omit this field.
|
|
meta_description:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Optional SEO description (word-safe truncate). Distinct from body
|
|
description when possible. Some companies omit this field.
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Generated product body as one HTML string — never a JSON array and
|
|
never a one- or two-sentence slogan. AI enhance follows the assigned
|
|
category description_template (description formula): ordered sections
|
|
of type h1, h2, h3, h4, p, and ul, each with its own instructions.
|
|
Output can be a short paragraph, several 100-word blocks, or a very
|
|
large multi-section article. When no formula is set, expect 1-3
|
|
factual paragraphs with limited HTML. Optional meta_description stays
|
|
plain SEO text and is not a substitute for this field.
|
|
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'
|
|
ProcessCompletedExample:
|
|
summary: "Completed poll item with category, description, and eprel"
|
|
value:
|
|
data:
|
|
status: COMPLETED
|
|
process_id: 8f14e45f-ceea-467a-9e5d-9c6b0e8f3a21
|
|
processing_type: full
|
|
items:
|
|
- ean: '8606019604493'
|
|
status: processed
|
|
category: Cookers
|
|
category_id: '50'
|
|
category_name: Cookers
|
|
name: VOX electric cooker EHT 6020 WG
|
|
description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
|
|
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'
|
|
ProcessInProgressExample:
|
|
summary: In-progress 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'
|
|
ProcessFailedExample:
|
|
summary: Failed 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 envelope).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CodedAPIError"
|
|
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/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Dashboard-shared handlers
|
|
value:
|
|
error: not found
|
|
coded:
|
|
summary: Coded 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/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error helper
|
|
value:
|
|
error: invalid json
|
|
coded:
|
|
summary: Coded 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/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat validation
|
|
value:
|
|
error: invalid id
|
|
coded:
|
|
summary: 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/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Middleware limiter
|
|
value:
|
|
error: rate limit exceeded
|
|
coded:
|
|
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/CodedAPIError"
|
|
examples:
|
|
flat:
|
|
summary: Flat Error helper
|
|
value:
|
|
error: list failed
|
|
coded:
|
|
summary: Coded internal error
|
|
value:
|
|
error:
|
|
code: internal_error
|
|
message: list failed
|
|
`)
|