Skip to content

Getting started

The OnPoint Delivery API lets you create deliveries, retrieve live status, manage cancellations, and receive real-time webhook notifications — without integrating directly with underlying carrier systems.

1. Obtain an API key

OnPoint issues a Bearer API key per merchant and environment (sandbox / production). Store it securely; never embed it in client-side code.

2. Choose your environment

EnvironmentPurpose
SandboxDevelopment and QA. No live drivers or customer SMS.
ProductionLive deliveries.

All requests use:

Authorization: Bearer YOUR_API_KEY

3. Discover capabilities

Before building checkout or dispatch UI, call GET /capabilities. Your tenant may or may not support quotes, returns, live driver location streaming, or line-item package details.

bash
curl "https://api.sandbox.getonpoint.io/delivery/v1/capabilities" \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response:

json
{
  "accountType": "marketplace",
  "capabilities": {
    "quotes": true,
    "marketplaceDispatch": true,
    "ownedFleetDispatch": false,
    "returns": true,
    "requirementEnforcement": true,
    "driverLocationStreaming": true,
    "routeOptimization": false,
    "itemLevelPackage": true,
    "cancellationFees": true,
    "maxRequestsPerSecond": 20
  }
}

4. Typical integration flow

mermaid
sequenceDiagram
  participant App as Your application
  participant OP as OnPoint API
  participant WH as Your webhook endpoint

  App->>OP: POST /serviceability (at address entry)
  OP-->>App: serviceable + gate + permitted proof types
  App->>OP: POST /quotes (optional)
  OP-->>App: quoteId + price options
  App->>OP: POST /deliveries
  OP-->>App: deliveryId + status accepted
  OP->>WH: delivery.driver_assigned
  OP->>WH: delivery.driver_enroute_to_pickup
  OP->>WH: delivery.delivered
  App->>OP: GET /deliveries/{deliveryId}
  OP-->>App: full delivery snapshot

Step A0 — Check serviceability

Call POST /serviceability when the customer enters an address, before you price anything. It costs nothing and answers a question a quote cannot: whether this stop is reachable at all, right now.

bash
curl -X POST "https://api.sandbox.getonpoint.io/delivery/v1/serviceability" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dropoff":{"address":"2175 Reilly Rd, Fort Liberty, NC 28310","installation":{"code":"INST-FTLIBERTY"}}}'

A serviceable: false result is a successful evaluation, not an error — check the field, not the status code. When the blocker is temporal (a closed gate rather than a restricted post), retryAfter tells you when to offer the slot instead.

This step earns its place on installation deliveries. A stop inside a military installation can only be served by a driver holding a current credential for that specific installation, and credentialedDriversAvailable tells you whether one is available before the customer commits. The alternative — which is what happens on platforms that do not model this — is the customer placing an order, waiting, and having it cancelled because the assigned courier turns out to have no base access, repeatedly.

Step A — Quote (optional)

If capabilities.quotes is true, request pricing before the customer commits:

bash
curl -X POST "https://api.sandbox.getonpoint.io/delivery/v1/quotes" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @examples/create-quote.json

Two non-success cases to handle:

  • Quoting not enabled for your account403 with code QUOTES_NOT_SUPPORTED. Check capabilities.quotes first and skip this step entirely.
  • No carrier can serve the delivery200 with an empty quotes array and a noQuoteReason (OUTSIDE_COVERAGE, NO_CAPACITY, OUTSIDE_HOURS, REQUIREMENT_UNSUPPORTED). This is a normal answer, not an error; surface it as "delivery unavailable" in checkout.

Step B — Create delivery

bash
curl -X POST "https://api.sandbox.getonpoint.io/delivery/v1/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f2b1c94-1f6a-4c3e-9f10-3d5b7a2e6c11" \
  -d @examples/create-delivery-asap.json

Idempotency-Key is required. Generate a fresh UUIDv4 per logical create — not per HTTP attempt — and reuse it on every retry of that create. Retrying with the same key and body replays the original response (200 with Idempotency-Replayed: true) instead of creating a second delivery. See Idempotency for the full state table.

Initial status: Inspect status in the response:

StatusMeaning
acceptedDelivery created; dispatch in progress
dispatch_failedCreated but no driver assigned yet — may resolve or become terminal
scheduledScheduled for a future window

Step C — Register webhooks

Configure an HTTPS endpoint in the OnPoint dashboard (or via your account manager). See Webhooks for signature verification and event catalog.

Step D — Poll or subscribe

Prefer webhooks for status updates. For reconciliation and recovery use GET /deliveries/{deliveryId}, GET /deliveries?clientOrderReference={ref} to find an order by your own ID, or GET /deliveries?from={lastSeenTimestamp} to sweep everything that changed while you were down.

If the gap was on the webhook side — your endpoint was down, misconfigured, or behind a bad deploy — use GET /events?delivered=false&since= instead. It returns exactly the events that never reached you, oldest first, with payloads identical to the originals, so you can replay them through the same handler rather than reconstructing state.

Step E — Test the whole thing

In sandbox, drive a delivery through its lifecycle yourself:

bash
curl -X POST ".../deliveries/del_.../simulate" \
  -H "Authorization: Bearer YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"advanceTo":"delivered"}'

Every intervening transition fires a real webhook, signed with your real signing secret — so your signature verification, the most error-prone part of the integration, is actually exercised. Transitions are validated against the production state machine, so an illegal jump fails here exactly as it would in production.

Test the unhappy paths too. {"advanceTo":"failed","statusReasonCode":"denied_at_gate"} exercises the branch that is hardest to reproduce for real and most likely to be wrong.

5. Units and formats

Field typeFormat
MoneyInteger cents (USD default)
WeightGrams
DimensionsCentimetres
PhoneE.164 (+14155551212)
TimestampsUTC ISO-8601 (2026-07-26T21:15:00Z)

6. Store pickups

For recurring pickup locations, register a store reference with OnPoint during onboarding. Then supply only pickup.storeReference instead of a full address:

json
{
  "pickup": {
    "storeReference": "STORE-SF-001"
  },
  "dropoff": {
    "address": "401 San Antonio Rd, Mountain View, CA 94040",
    "contactName": "Jane Customer",
    "contactPhone": "+14155559876"
  }
}

7. Directed dispatch (partner accounts only)

Skip this section unless capabilities.ownedFleetDispatch is true. For a standard merchant account it is false, GET /teams returns an empty array, and you should omit the dispatch block entirely — OnPoint recruits drivers, assigns them by market, and routes your delivery. That is the product; there is nothing to configure.

Directed dispatch exists for platform partners and the OnPoint dashboard, which run their own dispatch against the OnPoint fleet. Those accounts may pin a delivery to a team:

json
{
  "dispatch": {
    "mode": "team_queue",
    "teamReference": "TEAM-EAST-BAY"
  }
}

Sending team_queue or auto_assign without the capability returns 422 UNSUPPORTED_REQUIREMENT rather than quietly falling back to marketplace, so a partner can never believe a delivery was pinned when it was not.

Partner accounts can also map pickup.storeReference to a team during onboarding and omit the block per request.

8. Specifying verification

Verification is declared per stop and per mechanism, each with a level of required, preferred, or none:

json
{
  "verification": {
    "dropoff": {
      "signature": { "level": "required", "collectSignerName": true },
      "identity": { "level": "required", "minimumAge": 21 },
      "photo": { "level": "preferred", "substituteWhenProhibited": "signature" }
    }
  }
}

preferred is the level worth understanding. required means the stop fails if the check cannot be satisfied; preferred means it is attempted and the delivery still completes if it cannot be. Without that middle level you would be choosing between failing a delivery over a missing signature and never asking for one.

Two things happen to your request before it is enforced:

  1. Cargo attributes add requirements. Declaring package.contains: ["alcohol"] raises an identity check with minimumAge: 21 whether or not you asked for one. You do not have to encode compliance rules in your checkout, and you cannot accidentally omit them.
  2. Destination policy can remove them. Where photography is prohibited — true across much of a military installation — photo capture is suppressed and substituteWhenProhibited is enforced instead.

Read resolvedVerification on the delivery to see what will actually be enforced, including a derived[] list of what was added and a suppressed[] list of what was refused and why. Never assume your request was applied verbatim; a suppressed photo POD reported here is very different from a carrier that simply forgot.

9. Delivering onto a military installation

A street address is not enough inside an installation. Use the structured blocks rather than packing detail into instructions, which is capped at 280 characters and is not dispatchable:

json
{
  "dropoff": {
    "address": "2175 Reilly Rd, Fort Liberty, NC 28310",
    "installation": { "code": "INST-FTLIBERTY" },
    "unit": {
      "buildingNumber": "C-3421",
      "room": "214B",
      "deliveryPoint": "cq_desk"
    }
  },
  "handling": {
    "handoff": "cq_desk",
    "undeliverableAction": "fallback_handoff",
    "fallbackHandoff": "visitor_center"
  }
}

What installation.code buys you is dispatch filtering: the order is only offered to drivers holding a current credential scoped to that installation. Omit it and the stop is treated as an ordinary commercial address, which is how orders end up assigned to drivers who cannot get through the gate.

Four things to know:

  • cq_desk is the Charge of Quarters desk — the staffed 24-hour desk in a barracks, and the only lawful handoff point in most of them, since drivers cannot reach individual rooms.
  • Set a fallbackHandoff. Gate denial is routine, not exceptional. A driver turned away with no pre-agreed fallback has to improvise, and that is how packages get abandoned or returned at full cost.
  • Access policy changes fast. Force protection posture can tighten with under a day's notice. Subscribe to installation.access_policy_changed rather than caching serviceability across a scheduled window.
  • APO/FPO/DPO addresses are USPS-only. No commercial carrier can serve them. OnPoint rejects them at quote time with 422 UNSUPPORTED_DESTINATION rather than accepting the order and failing later.

Enumerate coverage with GET /installations, and read one installation's gates, hours, permitted proof types, and prohibited cargo with GET /installations/{code}.

10. Build a tolerant client

OnPoint fulfils deliveries through its own fleet in some markets and partner carrier networks in others. The contract is identical either way — no carrier-specific fields are ever exposed — so never branch on which carrier serves a market.

The major version in the base path (/delivery/v1) changes only for breaking changes. These ship without notice and your client must absorb them:

  • New optional request fields
  • New properties on response objects
  • New values in status, eventType, and error code enums
  • New webhook event types and endpoints

Two rules follow: ignore unknown properties instead of rejecting them, and handle unknown enum values with a default branch instead of throwing. A client that crashes on an unrecognized delivery status breaks the first time we add one.

Breaking changes ship as /delivery/v2 with at least 12 months of parallel v1 operation.

Next steps