API docs

DPPC Client API v1

English-only documentation for client integrations with API keys, pagination, filters, rule-based preview validation and webhooks.

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:read
  • products:write
  • materials:read
  • materials:write
  • documents:read
  • documents:write
  • suppliers:read
  • suppliers:write
  • certificates:read
  • certificates:write
  • passports:read
  • industries:read
  • sync:read
  • sync: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_slug to send,
  • which dynamic_data keys 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:

  • search
  • page
  • per_page

Products

  • GET /products
  • POST /products
  • GET /products/{id}
  • PUT /products/{id}
  • PATCH /products/{id}
  • GET /products/{id}/passport
  • POST /products/{id}/actions/{action}

Supported workflow actions:

  • approve
  • reject
  • publish
  • archive

Supported list filters:

  • status
  • search
  • industry_id
  • page
  • per_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}/materials
  • POST /products/{id}/materials
  • PUT /materials/{id}
  • PATCH /materials/{id}

Supported list filters:

  • kind
  • search
  • page
  • per_page

Product documents

  • GET /products/{id}/documents
  • POST /products/{id}/documents
  • POST /products/{id}/documents/preview

Document upload uses multipart/form-data.

Supported list filters:

  • document_type
  • is_public
  • search
  • page
  • per_page

Suppliers

  • GET /suppliers
  • POST /suppliers
  • GET /suppliers/{id}
  • PUT /suppliers/{id}
  • PATCH /suppliers/{id}

Supported list filters:

  • status
  • country
  • search
  • page
  • per_page

Certificates

  • GET /certificates
  • POST /certificates
  • GET /certificates/{id}
  • PUT /certificates/{id}
  • PATCH /certificates/{id}
  • POST /ai-preview/certificates

Certificate upload supports multipart/form-data.

Supported list filters:

  • status
  • issuer
  • is_public
  • search
  • page
  • per_page

Sync jobs

  • GET /sync-jobs
  • POST /sync-jobs
  • GET /sync-jobs/{id}

Supported list filters:

  • status
  • job_type
  • page
  • per_page

Supported inbound job types:

  • products.upsert
  • suppliers.upsert
  • certificates.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/products
  • POST /ai-preview/suppliers
  • POST /ai-preview/certificates
  • POST /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:

  • page
  • per_page with a maximum of 100

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:

  • warnings explain what looks risky or incomplete,
  • confidence is a simple score based on matched signals and warnings,
  • suggestions contain field-level proposed values,
  • extracted_fields contain 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_only sync 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:

  1. Resolve the industry schema with GET /industries/{slug}/product-template.
  2. Build the product payload with industry_slug and dynamic_data.
  3. Send the payload to POST /ai-preview/products.
  4. Show warnings, confidence and suggestions to 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:

  1. Collect basic certificate metadata.
  2. Upload the PDF to POST /ai-preview/certificates.
  3. Use extracted_fields and suggestions to prefill missing values.
  4. 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:

  1. Build a batch with job_type.
  2. Set mode=preview_only.
  3. Inspect result.items[].ai_review for every row.
  4. 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:

  1. Run the preview stage first.
  2. Accept or apply field-level suggestions.
  3. Resubmit the corrected payload to the write endpoint or sync job in commit mode.
  4. 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:

  1. Create or update the product.
  2. Attach the required documents and certificates.
  3. Check readiness in the product payload or fetch the product again.
  4. 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 409 as 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-Key only 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:

  1. wait 1-2s
  2. then 3-5s
  3. then 8-13s
  4. 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_slug and dynamic_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:

  1. run preview_only first
  2. fix invalid or low-confidence rows
  3. submit the corrected batch in commit mode
  4. 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-Id
  • X-DPPC-Delivery
  • X-DPPC-Event
  • X-DPPC-Signature

The signature format is:

sha256=<hex hmac of the raw JSON body>

Supported webhook events:

  • *
  • product.created
  • product.updated
  • product.deleted
  • product.published
  • supplier.created
  • supplier.updated
  • supplier.deleted
  • certificate.created
  • certificate.updated
  • certificate.deleted
  • material.created
  • material.updated
  • material.deleted
  • document.created
  • document.updated
  • document.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:

  1. Create a public HTTPS endpoint in the client system.
  2. Add the webhook URL in Organization settings > Webhooks.
  3. Select the events you want to receive.
  4. 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-Id
  • X-DPPC-Delivery
  • X-DPPC-Event
  • X-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 401 or 403 when the signature is invalid

3. Acknowledge fast, process asynchronously

Recommended flow:

  1. Verify the signature.
  2. Persist the raw payload and delivery ID.
  3. Return 2xx quickly.
  4. 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:

  1. wait 30s
  2. then 2m
  3. then 10m
  4. then move to manual review

6. Example receiver flow

Recommended receiver algorithm:

  1. read raw body
  2. read X-DPPC-Signature
  3. compute HMAC with webhook secret
  4. compare signatures
  5. store X-DPPC-Delivery + payload
  6. enqueue async processing
  7. return 204 No Content

7. Use test deliveries before production rollout

Before going live:

  1. create the webhook
  2. use the in-app test delivery button
  3. verify signature handling
  4. verify payload persistence
  5. verify async processing
  6. 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_data keys are rejected,
  • required fields are enforced,
  • number fields must be numeric,
  • checkbox fields must be boolean,
  • select fields must use one of the allowed options returned by GET /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:

  • name
  • certificate_number
  • issuer
  • issued_at
  • valid_until
  • status
  • is_public
  • file (optional PDF)

If a PDF is attached, DPPC may:

  • extract missing certificate fields,
  • return suggestions,
  • return ai_review warnings when the document content does not match the submitted record.

Product document create example

Use multipart/form-data with:

  • title
  • document_type
  • is_public
  • file (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

  • 401 invalid or missing API key
  • 403 API key does not include the required scope
  • 404 resource does not belong to the organization behind the key
  • 422 validation error
  • 429 rate 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, published and archived should 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.