> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sparqle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pickup points

> Let a shopper collect their parcel from a pickup point or parcel locker instead of at home.

A pickup point (also called a PUDO — pick-up / drop-off point) is a shop, post
office, or parcel locker where a shopper collects their parcel. Sparqle looks up
the points near the shopper, and you attach the one they pick to the order.

## The flow

<Steps>
  <Step title="Search points near the shopper">
    `GET /pickup-points` with the shopper's country and postal code, or with
    coordinates. You get the closest points, ordered by distance.
  </Step>

  <Step title="Let the shopper pick one">
    Render the list at checkout. Keep the `pudoId` of the point they choose.
  </Step>

  <Step title="Create the order with that point">
    `POST /orders` with the same `pudoId`, alongside the shopper's own delivery
    address as usual.
  </Step>
</Steps>

```mermaid theme={null}
flowchart TD
    A[Checkout] --> B[GET /pickup-points]
    B --> C{Any points?}
    C -->|Yes| D[Shopper picks one] --> E["POST /orders with pudoId"]
    C -->|No| F[Offer home delivery]
    E --> G{Pickup point delivery available?}
    G -->|Yes| H[Order created] --> I[GET label]
    G -->|No| J[406 — order rejected]
```

## Search for points

```bash theme={null}
curl "https://staging-v2.sparqle.tech/pickup-points?countryCode=NL&postalCode=1082ME&maxResults=5" \
  -H "api-key: $SPARQLE_API_KEY"
```

Search by `countryCode` + `postalCode`, or by `lat` + `lng`. Everything else is
optional: `city`, `street`, `state`, `maxResults`, `maxDistanceKm`, and `types`.

```json theme={null}
{
  "points": [
    {
      "pudoId": "9f83c1b0a4",
      "carrierPudoId": "8004567",
      "type": "SERVICE_POINT",
      "name": "Albert Heijn Zuidas",
      "address": {
        "street": "Gustav Mahlerlaan 320",
        "postalCode": "1082ME",
        "city": "Amsterdam",
        "countryCode": "NL"
      },
      "geo": { "lat": 52.3382, "lng": 4.8721 },
      "openingHours": {
        "open24h": false,
        "weekdays": { "monday": [{ "openTime": "09:00", "closeTime": "18:00" }] }
      },
      "facilities": { "carParking": true, "accessibility": true },
      "distanceMeters": 420
    }
  ],
  "totalFound": 12
}
```

`totalFound` counts the points near that address; when it exceeds the number of
points returned, `maxResults` truncated the list.

### Which id do I send?

**Always `pudoId`** — it goes straight through to the order field of the same
name, unchanged. Treat it as an opaque string: store it as-is and don't parse or
derive anything from its shape, which can change.

`carrierPudoId` is the **delivering carrier's** own identifier for that same
physical point (a DHL or PostNL location code, say). It is informational: send
it as the order's `pudoId` and the order will not route. It's there for one job —
matching. If your webshop plugin already let the shopper pick a point and stored
the *carrier's* id, search this endpoint for that address, find the point whose
`carrierPudoId` matches, and order with its `pudoId`. Match within one carrier:
these ids are unique per carrier, not across them.

### Filtering by kind

`types` narrows the search. Repeat the parameter for several kinds, or leave it
off to search all of them:

```
?countryCode=NL&postalCode=1082ME&types=PARCEL_LOCKER&types=PICKUP_DROPOFF_POINT
```

The filter values are broader than the `type` a point comes back with:

| `types` filter         | matches points of `type`       |
| ---------------------- | ------------------------------ |
| `PARCEL_LOCKER`        | `PARCEL_LOCKER`                |
| `PICKUP_DROPOFF_POINT` | `SERVICE_POINT`, `POST_OFFICE` |
| `MAILBOX_POBOX`        | `MAILBOX`                      |

## Create the order

Set `pudoId` on the order. The delivery address stays the **shopper's own
address** — the pickup point is identified by its id, not by using its address
as the destination.

```bash theme={null}
curl -X POST "https://staging-v2.sparqle.tech/orders" \
  -H "api-key: $SPARQLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderRef": "ORDER-1001",
    "locationId": "XXXXXXXXX",
    "deliveryName": "Jane Doe",
    "deliveryEmail": "jane@example.com",
    "pudoId": "9f83c1b0a4",
    "deliveryAddress": {
      "street": "Gustav Mahlerlaan",
      "houseNumber": "320",
      "postalCode": "1082ME",
      "city": "Amsterdam",
      "countryCode": "NL"
    }
  }'
```

## What makes a point available

Two separate things decide whether pickup point delivery works, and they can
disagree — always handle the rejection below rather than assuming a searchable
point is an orderable one.

<Steps>
  <Step title="The search reflects the pickup point network">
    `GET /pickup-points` returns whatever points exist near the address you
    searched. It is not scoped to your account, your location, or your delivery
    areas — the same address returns the same points for everyone.
  </Step>

  <Step title="Creating the order depends on your delivery areas">
    An order with a `pudoId` is only accepted when the delivery address falls in
    a delivery area that both is served by our pickup point carrier and has the
    **pickup point** capability enabled. That capability is set per delivery
    area, so pickup point delivery is effectively switched on per location — the
    locations linked to those areas. You can see which capabilities an area has
    on the location's page in the dashboard, under its delivery areas.
  </Step>
</Steps>

To find out before you create the order, pass `pickupPoint: true` to
[`POST /orders/check/{locationId}`](/concepts/routing) — it answers the same
question the create call would, and reports `missingCapabilities.pickupPoint`
when the address can't be served.

An order that names a pickup point is never quietly delivered to the door
instead: a locker id means nothing to another carrier, so we reject the order
rather than fall back.

```json theme={null}
{
  "statusCode": 406,
  "message": "No carrier offering pickup point delivery is available for this address.",
  "error": "Not Acceptable"
}
```

## Empty results

An empty `points` list is a normal answer, not an error. It means one of:

* there are no pickup points near that address,
* the country isn't covered by the pickup point network,
* the search radius (`maxDistanceKm`) or `types` filter excluded everything,
* the lookup itself failed — we return an empty list rather than an error so
  checkout keeps working.

Offer home delivery when the list comes back empty.
