Withdraw a published DPP (Approved -> Withdrawn -> public withdrawal notice)
POST/api/v1/products/:id/withdraw
Required scope: dpp:withdraw (distinct least-privilege scope: it changes
what an already-published passport shows). A WRITE (HMAC + Idempotency-Key).
The public DPP shows a withdrawal notice within the cache tick: HTTP 200,
with an availability block and X-DPP-Availability: archived on
/api/dpp/{slug}. A Counterfeit withdrawal shows the notice only; every
other reason keeps the passport under the notice. A
successorProductId is REQUIRED for reasons MarketWithdrawal /
Supersession and optional for EndOfLife. That is a platform continuity
rule and NOT a regulatory one: those three reasons are commercial and
assert no regulatory basis. Recall, SafetyDefect and NonCompliance carry
regulator-notify metadata (regulatorNotifiedAuthority); Counterfeit
does not. Under Reg. (EU) 2023/1542 Art. 38(9) a
manufacturer must inform the market surveillance authority immediately
where the battery presents a risk. Per-reason legal bases live in
src/lib/dpp/withdrawal-legal-basis.ts. The successor must be your own
product or a publicly Approved DPP. A RegulatoryDirective withdrawal
REQUIRES freezeOrdered and directiveAuthority and accepts an
optional directiveReference; all three are refused for any other
reason. BMS updates to the product are refused afterwards unless
freezeOrdered is false. None of the three is shown publicly.
Request
Responses
- 200
- 400
- 401
- 403
- 404
- 409
- 422
- 429
- 503
Withdrawn
400 — INVALID_JSON, VALIDATION_ERROR, VALIDATION (the {id} path segment is not a UUID), or IDEMPOTENCY_KEY_REQUIRED.
401 — INVALID_SIGNATURE or SIGNING_NOT_CONFIGURED (or bearer auth failure).
403 — missing scope (FORBIDDEN_SCOPE) or module (FORBIDDEN_MODULE).
404 — not found OR owned by another tenant (NOT_FOUND; IDOR-safe).
Product is not in a withdrawable state (INVALID_STATE), or its status changed while the request was processed (STATUS_CHANGED; re-read the product, then retry).
Successor invalid (SUCCESSOR_INVALID) or demo product (DEMO_PRODUCT).
429 — RATE_LIMITED (per-key limit, reads and writes alike; fails closed,
so a limiter outage also returns 429). A limiter-down 429 is deliberately
indistinguishable from a quota 429. Carries Retry-After (seconds).
Response Headers
Seconds to wait before retrying (429 only).
Per-minute request ceiling for this key's tier.
Requests left in the current window (floored at 0).
Seconds until the window resets.
503 — the API is not enabled on this environment (FEATURE_DISABLED).