{"openapi":"3.1.0","info":{"title":"Ascendons DPP API","version":"1.0.0","description":"Versioned REST API for product data and passport integration. Authenticate with an API key created in Integration Hub → API keys (`Authorization: Bearer adpp_…`). Rate limit: 300 requests per key per minute (RateLimit-* headers). Every response carries X-Correlation-Id. Breaking changes ship under a new major path (/api/v2); deprecated endpoints send Deprecation and Sunset headers for at least 6 months. Internal notes, costs, supplier contacts and commercial terms are never returned. Pushed records are reviewed in Ascendons; nothing is published through the API."},"servers":[{"url":"/api/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key with the scopes listed per operation."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","correlationId"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"correlationId":{"type":"string"}}}}},"ProductSummary":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string"},"status":{"type":"string","enum":["DRAFT","REVIEW","PUBLISHED","NEEDS_ATTENTION"]},"readinessScore":{"type":"integer","description":"Internal data-readiness indicator (0–100). Not a compliance determination."},"name":{"type":"string"},"productCode":{"type":["string","null"]},"sku":{"type":["string","null"]},"gtin":{"type":["string","null"]},"brand":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SyncRun":{"type":"object","properties":{"id":{"type":"string"},"connectionId":{"type":"string"},"trigger":{"type":"string"},"status":{"type":"string","enum":["RUNNING","SUCCEEDED","PARTIAL","FAILED"]},"correlationId":{"type":"string"},"mappingVersion":{"type":["integer","null"]},"counts":{"type":"object","properties":{"total":{"type":"integer"},"created":{"type":"integer"},"updated":{"type":"integer"},"unchanged":{"type":"integer"},"skipped":{"type":"integer"},"rejected":{"type":"integer"},"conflicts":{"type":"integer"}}},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["VALIDATION","CONFLICT","DUPLICATE","LOCKED","STALE","AMBIGUOUS","ERROR"]},"externalId":{"type":["string","null"]},"recordIndex":{"type":["integer","null"]},"field":{"type":["string","null"]},"message":{"type":"string"},"status":{"type":"string","enum":["OPEN","RESOLVED","DISMISSED"]}}}}}},"SourceRecord":{"type":"object","required":["fields"],"properties":{"externalId":{"type":"string","description":"Stable id in your system. Alternatively send it in the field named by the mapping's externalIdField."},"modifiedAt":{"type":"string","format":"date-time","description":"Source modification time. Older versions than the last applied are skipped."},"fields":{"type":"object","additionalProperties":{"type":["string","number","boolean","null"]},"description":"Source fields, interpreted through the connection's field mapping. Default REST mapping: name, productCode, sku, gtin, brand, model, description, manufacturerName, manufacturerAddress, countryOfManufacture, composition (e.g. \"85% Cotton, 15% Polyester\"). Empty values never clear data."}}}}},"paths":{"/products":{"get":{"summary":"List products (oldest change first)","description":"Scope: products:read. Read incrementally with updatedSince and the returned nextCursor.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["DRAFT","REVIEW","PUBLISHED","NEEDS_ATTENTION"]}},{"name":"updatedSince","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"A page of products","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"}},"nextCursor":{"type":["string","null"]}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products/{id}":{"get":{"summary":"Get a product with materials, versions, external references and field provenance","description":"Scope: products:read.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The product"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products/{id}/passport":{"get":{"summary":"Get the currently published passport (public projection)","description":"Scope: passports:read. Same fields as the public QR page; internal sections are excluded.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The published passport"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/connections/{id}/records":{"post":{"summary":"Push product records through a REST API connection","description":"Scope: products:write. Requires an Idempotency-Key header; repeating a key returns the original run (200, Idempotent-Replayed: true) and reusing it with a different body returns 422. Records are matched by external id (then by the mapping's matchOn identifier), validated, and applied to draft data only. Conflicting values are queued for review; published passports are never changed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["records"],"properties":{"records":{"type":"array","minItems":1,"maxItems":250,"items":{"$ref":"#/components/schemas/SourceRecord"}}}}}}},"responses":{"200":{"description":"Idempotent replay of an earlier run"},"201":{"description":"Run completed","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SyncRun"},"replayed":{"type":"boolean"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sync-runs/{id}":{"get":{"summary":"Get a sync run and its issues","description":"Scope: sync:read.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"product.created":{"post":{"summary":"A product was created (manually, by AI import confirmation or by a sync)","description":"data: { productId, name, category, source }"}},"passport.published":{"post":{"summary":"A passport version was published","description":"data: { productId, name, versionNumber, supersededVersion, publishedAt }"}},"sync.run.completed":{"post":{"summary":"A synchronisation run finished","description":"data: { runId, connectionId, status, trigger, counts }"}}},"x-webhook-delivery":{"body":"{ id, type, createdAt, data }","headers":{"Ascendons-Event-Id":"Unique event id; de-duplicate on it (deliveries are at-least-once).","Ascendons-Event-Type":"Event type.","Ascendons-Delivery-Attempt":"1-based attempt number.","Ascendons-Signature":"t=<unix seconds>,v1=<hex HMAC-SHA256 of `${t}.${body}` with the endpoint secret>. Reject if older than 5 minutes."},"retries":"Non-2xx responses and timeouts (10 s) are retried after 1 min, 5 min, 30 min, 2 h and 6 h; after 6 attempts the delivery is marked failed and can be retried from the Integration Hub."}}