Appearance
Admin API endpoints
For developers
The plugin adds three Admin API actions, used by the Administration's Delivery verification card and available to a rider app or other integration. There are no storefront routes and no webhooks.
Authentication
All three are POST, need an Admin API token (user or integration) and the privilege lastmiledelivery.manage (_acl on the route). Grant it with the role permission Last-Mile Delivery for Shopware 6 → Manage, which also carries the entity privileges the actions write with: kmh_lastmiledelivery_order_otp:create/update, order_delivery:update, state_machine_history:create, media:create/update and others — see src/Resources/app/administration/src/acl/index.js.
The body is JSON; form parameters are accepted too. Responses carry only a coarse result — never the code, its hash or remaining attempts.
The endpoints don't check the delivery status: unlike the card, the API lets you verify or attach a photo while a delivery is not In transit.
POST /api/_action/kmh-lastmiledelivery/order/{orderId}/verify-otp
Checks a delivery code. On success the record is marked verified, kmh_lastmiledelivery.order_otp.verified is dispatched (default flow: delivery confirmed email) and the auto-ship decision runs.
json
{ "code": "123456" }| Status | result | When |
|---|---|---|
200 | success | Correct code; verified: true |
422 | invalid_code | Wrong code (an attempt is used), or a non-digit / empty code (no attempt used) |
422 | expired | Validity time passed |
422 | already_verified | Already verified (also when two correct codes race — only one wins) |
429 | max_attempts_reached | Attempt limit reached |
404 | not_found | No verification record, or a record without a code (photo only) |
json
{ "result": "invalid_code", "verified": false }Checks run in this order: already verified → expired → attempts exhausted → code compared in constant time. Each scored guess claims an attempt with one conditional UPDATE, so parallel requests can't exceed the limit.
POST /api/_action/kmh-lastmiledelivery/order/{orderId}/resend-otp
Generates a new code with a new expiry, resets attempts, invalidates the old code (or creates a record if none exists) and dispatches kmh_lastmiledelivery.order_otp.generated again. No body. Doesn't check Enable delivery OTP.
| Status | result | When |
|---|---|---|
200 | reissued | reissued: true |
409 | already_verified | The record is already verified |
404 | not_found | Order not found |
POST /api/_action/kmh-lastmiledelivery/order/{orderId}/proof-of-delivery
Links an existing media entity (upload it first with Shopware's media API) as proof of delivery, then runs the auto-ship decision. Re-submitting replaces the reference.
json
{ "mediaId": "0190…" }| Status | result | When |
|---|---|---|
200 | attached | attached: true |
400 | media_not_found | mediaId missing or empty |
404 | order_not_found / media_not_found | Unknown order or media |
409 | disabled | Enable proof of delivery image is off for the order's sales channel |
422 | not_an_image | Media type isn't an image (and MIME type doesn't start with image/) |
422 | image_predates_order | Media uploadedAt is empty or before the order date |
Example
bash
curl -X POST "https://shop.example/api/_action/kmh-lastmiledelivery/order/$ORDER_ID/verify-otp" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"code":"123456"}'Entities over the Admin API
The plugin's entities are readable and writable through the generic Admin API (/api/kmh-lastmiledelivery-delivery-price-rule, /api/search/kmh-lastmiledelivery-order-otp, …) with the matching privileges. otpHash is not API-aware and is never returned. Field list: Architecture → Data model.