DPPC Client API v1
This document describes the first client-facing API for DPPC.
Public docs page:
https://dppc.pl/api-docs
Downloadable OpenAPI schema:
https://dppc.pl/api-docs/openapi.yaml
Authentication
Use an organization API key generated in Organization settings > Client API keys.
Send the token as a bearer token:
Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Base URL
https://dppc.pl/api/v1
Available scopes
products:readproducts:writematerials:readmaterials:writedocuments:readdocuments:writesuppliers:readsuppliers:writecertificates:readcertificates:writepassports:readindustries:readsync:readsync:write
Endpoints
Context
GET /context
Returns the current organization, active key metadata and API version details.
Industries
GET /industries
GET /industries/{slug}/product-template
Returns active industries with translated English field definitions and select options. This is the endpoint a client should call first before creating products. It tells the client:
- which
industry_slugto send, - which
dynamic_datakeys are allowed, - which dynamic fields are required,
- which select options are allowed.
GET /industries/{slug}/product-template returns:
- an empty valid payload template,
- an example payload for that industry,
- the resolved industry metadata.
Supported filters:
searchpageper_page
Products
GET /productsPOST /productsGET /products/{id}PUT /products/{id}PATCH /products/{id}GET /products/{id}/passportPOST /products/{id}/actions/{action}
Supported workflow actions:
approverejectpublisharchive
Supported list filters:
statussearchindustry_idpageper_page
For writes, send industry_slug as the preferred identifier. industry_id is still accepted, but industry_slug is safer for external clients.
Product materials
GET /products/{id}/materialsPOST /products/{id}/materialsPUT /materials/{id}PATCH /materials/{id}
Supported list filters:
kindsearchpageper_page
Product documents
GET /products/{id}/documentsPOST /products/{id}/documentsPOST /products/{id}/documents/preview
Document upload uses multipart/form-data.
Supported list filters:
document_typeis_publicsearchpageper_page
Suppliers
GET /suppliersPOST /suppliersGET /suppliers/{id}PUT /suppliers/{id}PATCH /suppliers/{id}
Supported list filters:
statuscountrysearchpageper_page
Certificates
GET /certificatesPOST /certificatesGET /certificates/{id}PUT /certificates/{id}PATCH /certificates/{id}POST /ai-preview/certificates
Certificate upload supports multipart/form-data.
Supported list filters:
statusissueris_publicsearchpageper_page
Sync jobs
GET /sync-jobsPOST /sync-jobsGET /sync-jobs/{id}
Supported list filters:
statusjob_typepageper_page
Supported inbound job types:
products.upsertsuppliers.upsertcertificates.upsert
Supported modes:
commit(default)preview_only
Preview validation
The /ai-preview/... paths are historical names. These endpoints run rule-based checks, not an AI model.
POST /ai-preview/productsPOST /ai-preview/suppliersPOST /ai-preview/certificatesPOST /products/{id}/documents/preview
These endpoints let the client validate data before any write happens.
Use them when you want:
- warnings before save,
- a simple confidence score,
- values read from PDFs that have a plain text layer (no OCR for scans),
- suggested corrections or autofill values,
- safer import UX for external integrators.
Pagination
All list endpoints support:
pageper_pagewith a maximum of100
List responses now return:
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 120,
"last_page": 5,
"from": 1,
"to": 25
},
"links": {
"next": "https://dppc.pl/api/v1/products?page=2",
"prev": null
}
}
Pre-check response shape
Preview endpoints and document/certificate upload responses now return a structured pre-check envelope.
Example:
{
"ai_review": {
"status": "needs_review",
"warnings": [
"Certificate issuer is missing.",
"Certificate expires soon and may need immediate renewal planning."
],
"confidence": {
"score": 74,
"label": "medium"
},
"suggestions": {
"issuer": "FSC International",
"valid_until": "2027-01-15"
},
"extracted_fields": {
"certificate_number": "FSC-2026-001",
"issuer": "FSC International",
"issued_at": "2026-01-15",
"valid_until": "2027-01-15"
}
},
"warnings": [
"Certificate issuer is missing.",
"Certificate expires soon and may need immediate renewal planning."
],
"confidence": {
"score": 74,
"label": "medium"
},
"suggestions": {
"issuer": "FSC International",
"valid_until": "2027-01-15"
},
"extracted_fields": {
"certificate_number": "FSC-2026-001",
"issuer": "FSC International",
"issued_at": "2026-01-15",
"valid_until": "2027-01-15"
}
}
Pre-check result interpretation
The UI and API use the same interpretation model:
warningsexplain what looks risky or incomplete,confidenceis a simple score based on matched signals and warnings,suggestionscontain field-level proposed values,extracted_fieldscontain values found in readable PDF text (files without a text layer yield none).
Recommended client behavior:
- show warnings before save,
- render field diffs as
current -> suggested, - allow users to accept suggestions field by field,
- use
preview_onlysync jobs when onboarding large CSV batches.
Preview validation curl examples
Preview a product payload before save:
curl -X POST "https://dppc.pl/api/v1/ai-preview/products" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active",
"description": "Short desc"
}'
Preview a certificate PDF before save:
curl -X POST "https://dppc.pl/api/v1/ai-preview/certificates" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-F "name=FSC Chain of Custody" \
-F "certificate_number=" \
-F "issuer=" \
-F "status=valid" \
-F "file=@/absolute/path/to/fsc-certificate.pdf;type=application/pdf"
Preview a product document PDF before save:
curl -X POST "https://dppc.pl/api/v1/products/44/documents/preview" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-F "title=Assembly Manual" \
-F "document_type=manual" \
-F "is_public=1" \
-F "file=@/absolute/path/to/assembly-manual.pdf;type=application/pdf"
Sync job preview-only example
Use preview_only when you want the full pre-check results without writing records.
{
"job_type": "products.upsert",
"mode": "preview_only",
"items": [
{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active"
}
]
}
Equivalent curl request:
curl -X POST "https://dppc.pl/api/v1/sync-jobs" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"job_type": "products.upsert",
"mode": "preview_only",
"items": [
{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active"
}
]
}'
Example response:
{
"data": {
"id": 41,
"job_type": "products.upsert",
"status": "completed",
"mode": "preview_only",
"result": {
"items": [
{
"index": 0,
"status": "previewed",
"ai_review": {
"status": "needs_review",
"warnings": [
"Active product has no brand yet.",
"Active product has no model yet."
],
"confidence": {
"score": 70,
"label": "medium"
},
"suggestions": [
{
"field": "brand",
"label": "Brand",
"current": "",
"suggested": "Add the commercial brand shown on the product.",
"reason": "Active products should usually have a clear brand."
},
{
"field": "model",
"label": "Model",
"current": "",
"suggested": "Add the product model or internal model designation.",
"reason": "Model helps identify the exact variant in the passport."
}
]
}
}
]
}
}
}
Integration cookbook
1. Preview product payload
Recommended flow:
- Resolve the industry schema with
GET /industries/{slug}/product-template. - Build the product payload with
industry_sluganddynamic_data. - Send the payload to
POST /ai-preview/products. - Show
warnings,confidenceandsuggestionsto the user before save.
Example:
curl -X POST "https://dppc.pl/api/v1/ai-preview/products" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active",
"description": "Short desc"
}'
2. Preview certificate PDF
Recommended flow:
- Collect basic certificate metadata.
- Upload the PDF to
POST /ai-preview/certificates. - Use
extracted_fieldsandsuggestionsto prefill missing values. - Let the user accept the diff before creating the final certificate.
Example:
curl -X POST "https://dppc.pl/api/v1/ai-preview/certificates" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-F "name=FSC Chain of Custody" \
-F "certificate_number=" \
-F "issuer=" \
-F "status=valid" \
-F "file=@/absolute/path/to/fsc-certificate.pdf;type=application/pdf"
3. Batch preview_only
Recommended flow:
- Build a batch with
job_type. - Set
mode=preview_only. - Inspect
result.items[].ai_reviewfor every row. - Fix payloads before any write happens.
Example:
curl -X POST "https://dppc.pl/api/v1/sync-jobs" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"job_type": "products.upsert",
"mode": "preview_only",
"items": [
{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active"
}
]
}'
4. Commit after review
Recommended flow:
- Run the preview stage first.
- Accept or apply field-level suggestions.
- Resubmit the corrected payload to the write endpoint or sync job in commit mode.
- Store the returned IDs and workflow state in the client system.
Example product create:
curl -X POST "https://dppc.pl/api/v1/products" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7dd42a6a-85d4-47d8-8895-1f3f7f0a0e9d" \
-d '{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"brand": "Nordform",
"model": "Table Pro",
"origin_country": "PL",
"description": "Structured product record for public passport publishing.",
"status": "active",
"dynamic_data": {
"wood_type": "Oak"
}
}'
Example sync job commit:
curl -X POST "https://dppc.pl/api/v1/sync-jobs" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"job_type": "products.upsert",
"mode": "commit",
"items": [
{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"brand": "Nordform",
"model": "Table Pro",
"status": "active"
}
]
}'
5. Publish product after passing checks
Recommended flow:
- Create or update the product.
- Attach the required documents and certificates.
- Check readiness in the product payload or fetch the product again.
- Trigger the workflow action only after the product is ready for publication.
Example:
curl -X POST "https://dppc.pl/api/v1/products/44/actions/publish" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json"
If a product still fails readiness or workflow rules, the API will return a structured error envelope explaining what blocks the publish step.
Error handling and retries
All API errors use a consistent envelope:
{
"error": {
"code": "validation_failed",
"message": "The request payload is invalid.",
"details": {
"name": [
"The name field is required."
]
}
}
}
Recommended retry behavior
- Do retry
429,500,502,503,504 - Do not blindly retry
400,401,403,404,422 - Treat
409as a conflict that needs inspection before retry
Idempotency for write requests
For create or write operations, send an Idempotency-Key header.
Example:
curl -X POST "https://dppc.pl/api/v1/products" \
-H "Authorization: Bearer dppc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7dd42a6a-85d4-47d8-8895-1f3f7f0a0e9d" \
-d '{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"status": "active"
}'
Recommended client behavior:
- reuse the same
Idempotency-Keyonly for an exact retry of the same request - generate a new key for a logically new write
- if the same key is reused with a different payload, expect
409
Rate limits
The API applies rate limiting per API key, with different limits for read and write traffic.
If you receive 429 Too Many Requests:
- back off and retry later
- use exponential backoff with jitter
- avoid replay storms from batch jobs
Recommended retry timing:
- wait
1-2s - then
3-5s - then
8-13s - then move the job to delayed retry or manual review
Validation failures
For 422 or 400 payload issues:
- inspect
error.details - fix the payload
- rerun preview endpoints before committing writes
This is especially recommended for:
industry_sluganddynamic_data- certificate metadata
- product documents and PDF-backed records
- batch sync jobs in
preview_only
Workflow action failures
Workflow endpoints such as publish may fail even when the product exists.
Typical reasons:
- readiness score is still too low
- required documents are missing
- required certificate state is not valid
- the current workflow status does not allow the requested action
Recommended client behavior:
- fetch the product again
- inspect readiness and workflow status
- resolve blocking issues
- retry the workflow action only after state changes
Safe batch retry strategy
For sync-jobs:
- run
preview_onlyfirst - fix invalid or low-confidence rows
- submit the corrected batch in commit mode
- retry only the failed subset, not the entire original batch
This keeps retries smaller, safer and easier to audit.
Webhooks
Webhook subscriptions are managed in Organization settings > Webhooks.
You can trigger a manual test delivery from the webhook management UI in the application.
Each delivery sends:
X-DPPC-Webhook-IdX-DPPC-DeliveryX-DPPC-EventX-DPPC-Signature
The signature format is:
sha256=<hex hmac of the raw JSON body>
Supported webhook events:
*product.createdproduct.updatedproduct.deletedproduct.publishedsupplier.createdsupplier.updatedsupplier.deletedcertificate.createdcertificate.updatedcertificate.deletedmaterial.createdmaterial.updatedmaterial.deleteddocument.createddocument.updateddocument.deleted
Example payload:
{
"id": "4fd1f1dd-37f1-4f6d-b49f-7b74f8cc0d50",
"type": "product.updated",
"occurred_at": "2026-05-09T10:15:00Z",
"organization_id": 12,
"resource": {
"id": 44,
"name": "Oak Table",
"sku": "OAK-180",
"status": "active"
}
}
Webhook cookbook
1. Register a webhook endpoint
Recommended flow:
- Create a public HTTPS endpoint in the client system.
- Add the webhook URL in Organization settings > Webhooks.
- Select the events you want to receive.
- Store the webhook secret on the client side.
Use the in-app Send test action first before enabling production automation.
2. Verify the signature
Every webhook request includes:
X-DPPC-Webhook-IdX-DPPC-DeliveryX-DPPC-EventX-DPPC-Signature
The client should compute:
sha256=<hex hmac of the raw JSON body>
and compare it with X-DPPC-Signature.
Recommended behavior:
- reject requests with missing signature headers
- verify using the raw request body, not parsed JSON
- use a constant-time comparison
- return
401or403when the signature is invalid
3. Acknowledge fast, process asynchronously
Recommended flow:
- Verify the signature.
- Persist the raw payload and delivery ID.
- Return
2xxquickly. - Process business logic in a background job.
This avoids timeouts and duplicate downstream processing.
4. Deduplicate deliveries
Use X-DPPC-Delivery as the delivery-level identifier.
Recommended behavior:
- store processed delivery IDs
- ignore or safely short-circuit duplicates
- make downstream processing idempotent
5. Retry strategy on the client side
If your own downstream processing fails after you already acknowledged the webhook:
- retry inside your own job system
- do not ask DPPC to resend the same delivery as the primary recovery path
- keep retries idempotent on your side too
Recommended retry timing:
- wait
30s - then
2m - then
10m - then move to manual review
6. Example receiver flow
Recommended receiver algorithm:
- read raw body
- read
X-DPPC-Signature - compute HMAC with webhook secret
- compare signatures
- store
X-DPPC-Delivery+ payload - enqueue async processing
- return
204 No Content
7. Use test deliveries before production rollout
Before going live:
- create the webhook
- use the in-app test delivery button
- verify signature handling
- verify payload persistence
- verify async processing
- only then enable production workflows
This is the safest way to confirm the full outbound integration path.
Product create example
{
"name": "Oak Table",
"industry_slug": "furniture",
"sku": "OAK-180",
"brand": "Nordform",
"model": "Table Pro",
"gtin": "5900000000001",
"serial_number": "SER-001",
"batch_number": "BATCH-01",
"origin_country": "PL",
"description": "Structured product record for public passport publishing.",
"status": "active",
"dynamic_data": {
"wood_type": "Oak"
}
}
Furniture example payload
{
"name": "Furniture Product",
"industry_slug": "furniture",
"sku": "FUR-001",
"brand": "Example Brand",
"model": "Example Model",
"gtin": null,
"serial_number": null,
"batch_number": null,
"origin_country": null,
"description": "Example structured product record payload for API integration.",
"status": "active",
"dynamic_data": {
"wood_type": "Wood type example"
}
}
Product template response example
{
"data": {
"industry": {
"id": 1,
"slug": "furniture",
"name": "Furniture"
},
"template": {
"name": null,
"industry_slug": "furniture",
"sku": null,
"brand": null,
"model": null,
"gtin": null,
"serial_number": null,
"batch_number": null,
"origin_country": null,
"description": null,
"status": "active",
"dynamic_data": {
"wood_type": null
}
},
"example_payload": {
"name": "Furniture Product",
"industry_slug": "furniture",
"sku": "FUR-001",
"brand": "Example Brand",
"model": "Example Model",
"gtin": null,
"serial_number": null,
"batch_number": null,
"origin_country": null,
"description": "Example structured product record payload for API integration.",
"status": "active",
"dynamic_data": {
"wood_type": "Wood type example"
}
}
}
}
Dynamic field validation rules
Dynamic payloads are validated against the selected industry definition.
Rules:
- unknown
dynamic_datakeys are rejected, - required fields are enforced,
numberfields must be numeric,checkboxfields must be boolean,selectfields must use one of the allowed options returned byGET /industries,- text-like fields must be plain strings.
This means a client should first fetch the industry schema and then build the payload from that schema instead of guessing field names.
Product material create example
{
"kind": "material",
"name": "Solid oak",
"share_percentage": 80,
"origin_country": "PL",
"supplier_name": "Nordic Timber",
"recyclable": true
}
Sync job create example
{
"job_type": "products.upsert",
"records": [
{
"name": "Oak Table",
"industry_id": 1,
"sku": "OAK-180",
"status": "active",
"dynamic_data": {
"wood_type": "Oak"
}
}
]
}
Supplier create example
{
"name": "Nordic Timber",
"country": "PL",
"vat_id": "PL5250001122",
"email": "sales@nordictimber.example",
"status": "active"
}
Certificate create example
Use multipart/form-data with:
namecertificate_numberissuerissued_atvalid_untilstatusis_publicfile(optional PDF)
If a PDF is attached, DPPC may:
- extract missing certificate fields,
- return
suggestions, - return
ai_reviewwarnings when the document content does not match the submitted record.
Product document create example
Use multipart/form-data with:
titledocument_typeis_publicfile(required PDF)
Response shape
All list endpoints return:
{
"data": [],
"meta": {},
"links": {}
}
Single-resource endpoints return:
{
"data": {}
}
Some write endpoints also return:
{
"data": {},
"ai_review": {
"status": "ok",
"warnings": []
},
"suggestions": {}
}
Error responses
401invalid or missing API key403API key does not include the required scope404resource does not belong to the organization behind the key422validation error429rate limit or downstream retry pressure on webhook delivery consumers should be handled by the client on their side
Notes
- Product workflow states such as
approved,publishedandarchivedshould be changed through the workflow action endpoint, not through normal product update payloads. - Public passport and public asset URLs returned by the API use the same product/public document model as the web application.
- The API is organization-scoped. Every key can only access records that belong to the organization that created it.