EasyParcel
All articles
Platform

Platform API quick reference

Published 3 June 2026

Platform API — Reference & External-App Integration Guide

Freight aggregation platform (white-label: ParcelMax / eParcel). REST API under /api/v1/*, authenticated with OAuth 2.0 Client Credentials. This document describes the full API surface, scopes, authentication, and how an external application integrates.

Last updated: 2026-06-02.


1. The two API roles

The API is split into two functional roles, separated by who manages the credentials and by account scoping.

Role 1 — Platform IntegrationRole 2 — Customer / eCommerce
PurposeAn external system maintains customer accounts + addresses, and signs users in via SSOA 3rd-party ecommerce platform pushes orders and manages inventory
Managed bysystem_admin (Platform Integration settings)customer_admin (Customer API Management, per account)
Client bindingUsually system-wide (customer_account_id = null)Bound to a specific customer account
Scopescustomers:*, addresses:*, auth:ssoorders:*, inventory:*, addresses:validate
Auth methodsOAuth 2.0 onlyOAuth 2.0 or API key

Both roles use the same token endpoint and the same Bearer-token model; they differ only in which scopes the client is granted and which endpoints those scopes unlock.


2. Base URLs

The base URL is per tenant + environment. Example:

TenantEnvironmentBase URL
EasyParcelProductionhttps://online.easyparcel.co.nz
EasyParcelPreviewhttps://eparcel-preview.vercel.app

All paths in this document are relative to the base URL (e.g. POST /api/v1/oauth/token).


3. Authentication — OAuth 2.0 Client Credentials

3.1 Obtaining credentials (one-time)

An administrator provisions an OAuth client for your integration. You receive:

  • client_id — public identifier, e.g. oauth_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • client_secret — shown once at creation, stored only as a bcrypt hash. If lost, the client must be revoked and recreated.

The client also carries: its allowed scopes, an is_active flag, an optional IP allowlist, and a rate limit (default 60 req/min).

3.2 Step 1 — Get an access token

POST /api/v1/oauth/token

Accepts application/x-www-form-urlencoded or application/json.

FieldRequiredNotes
grant_typeyesMust be client_credentials
client_idyes
client_secretyes
scopenoSpace-separated subset of the client's allowed scopes. Omit to receive all of the client's scopes.
curl -X POST https://online.easyparcel.co.nz/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=oauth_xxxxxxxx" \
  -d "client_secret=yyyyyyyy" \
  -d "scope=customers:read customers:write"

Response 200 OK:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "customers:read customers:write"
}

The access_token is a JWT (HS256). Do not decode or modify it client-side — treat it as opaque.

Scope rule: if you request scope, the server grants only the intersection of your request and the client's allowed scopes. If that intersection is empty you get 400 invalid_scope. If you omit scope, you get all of the client's scopes.

3.3 Step 2 — Call the API with the token

curl https://online.easyparcel.co.nz/api/v1/customer-accounts \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

3.4 Token lifecycle

  • Tokens expire after 1 hour (expires_in: 3600).
  • Cache and reuse the token until shortly before expiry; request a new one as needed.
  • There is no refresh token — just request a new token with the client credentials.

3.5 What is validated on every API call

All of the following must pass or the request is rejected:

  1. JWT signature valid and not expired
  2. The client is still active (re-checked against the database, not just trusted from the token)
  3. The token carries a scope the endpoint requires
  4. If the client has an IP allowlist, the caller's IP is in it
  5. The client's rate limit is not exceeded

4. Scopes

The canonical set is 10 scopes:

ScopeRoleDescription
addresses:read1Read customer addresses
addresses:write1Create and update customer addresses
addresses:validate1 / 2Validate addresses using NZ Post
customers:read1Read customer account information
customers:write1Create and update customer accounts
orders:read2Read eCommerce orders and tracking
orders:write2Create and manage eCommerce orders
inventory:read2Read products, stock levels, and movements
inventory:write2Manage products, receive stock, and adjust inventory
auth:sso1Generate single-use login tokens for SSO integration

GET /api/v1/oauth/token returns this list in its supported_scopes field (machine-readable discovery).


5. Limits & error responses

Rate limiting

  • Per-client, default 60 requests/minute.
  • Every response includes: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
  • Exceeding it returns 429 with { "error": "rate_limit_exceeded" }.

Common errors

HTTPerrorMeaning
400invalid_requestMissing/invalid parameters
400unsupported_grant_typegrant_typeclient_credentials
400invalid_scopeRequested scope not allowed for this client
401invalid_clientBad client_id/client_secret, or client inactive
401(auth)Missing/expired/invalid Bearer token, or client deactivated
403(scope/IP)Token lacks the required scope, or caller IP not allowlisted
429rate_limit_exceededToo many requests

Error bodies follow { "error": "...", "error_description": "..." }.


6. Role 1 — Platform Integration endpoints

Requires a system-wide OAuth client with customers:* / addresses:* / auth:sso as needed. Where two scopes are listed, any one of them is sufficient.

Customer Accounts

MethodPathScope
GET/api/v1/customer-accountscustomers:read
POST/api/v1/customer-accountscustomers:write
GET/api/v1/customer-accounts/{id}customers:read
PUT/api/v1/customer-accounts/{id}customers:write
DELETE/api/v1/customer-accounts/{id}customers:write

Customer Addresses

MethodPathScope (any of)
GET/api/v1/customer-accounts/{id}/addressesaddresses:read or customers:read
POST/api/v1/customer-accounts/{id}/addressesaddresses:write or customers:write
GET/api/v1/customer-accounts/{id}/addresses/{addressId}addresses:read or customers:read
PUT/api/v1/customer-accounts/{id}/addresses/{addressId}addresses:write or customers:write
DELETE/api/v1/customer-accounts/{id}/addresses/{addressId}addresses:write or customers:write

Address Validation

MethodPathScope (any of)
POST/api/v1/addresses/validateaddresses:validate
GET/api/v1/addresses/details/{id}addresses:read or addresses:validate

Example — list customer accounts:

curl https://online.easyparcel.co.nz/api/v1/customer-accounts \
  -H "Authorization: Bearer <token>"
{
  "data": [
    { "id": "…", "account_number": "OMAX-001", "account_name": "OfficeMax",
      "contact_name": "…", "contact_email": "…", "is_active": true, "...": "..." }
  ],
  "pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
}

7. SSO Integration (auth:sso)

Lets a 3rd-party auth system sign a user into the platform without the user entering credentials.

Flow

  1. Get an OAuth token with the auth:sso scope (Section 3).

  2. Generate a single-use login token:

    POST /api/v1/auth/login-token (Bearer token required)

    FieldRequiredNotes
    emailyesIf the user doesn't exist, they are auto-created
    defaultAccountyesAccount number set as the user's active account on login; must exist and be in accounts
    accountsyesArray of account numbers to link the user to; unknown numbers are silently skipped
    namenoDisplay name; defaults to the email. Updated on every SSO call
    rolenocustomer or customer_admin (default customer_admin). Updated on every SSO call
    curl -X POST https://online.easyparcel.co.nz/api/v1/auth/login-token \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: application/json" \
      -d '{
        "email": "user@example.com",
        "name": "Jane Smith",
        "role": "customer_admin",
        "defaultAccount": "ACC002",
        "accounts": ["ACC002", "ACC003"]
      }'
    

    Response 201 Created:

    { "token": "771d86b0-bd86-4478-874a-4b016ea13658" }
    
  3. Redirect the user's browser to complete sign-in:

    https://online.easyparcel.co.nz/auth/sso?token=771d86b0-bd86-4478-874a-4b016ea13658
    

SSO security notes

  • Login tokens are single-use (consumed when the user lands on the SSO page).
  • They expire after 60 seconds — redirect promptly after generating.
  • New users are auto-created with a random password (login only via SSO or password reset thereafter).
  • role and name are updated on every SSO call, including for existing users.
  • defaultAccount must be present in the accounts array.

8. Role 2 — Customer / eCommerce endpoints

Requires an OAuth client bound to a customer account, with orders:* and/or inventory:*. These endpoints also accept an API key as an alternative to the Bearer token.

eCommerce Orders

MethodPathScopeDescription
POST/api/v1/ecommerce/ordersorders:writeCreate order for fulfillment
POST/api/v1/ecommerce/orders/cancelorders:writeCancel order
POST/api/v1/ecommerce/orders/fulfilledorders:writeMark order fulfilled externally
POST/api/v1/ecommerce/orders/refundedorders:writeCancel order due to refund
GET/api/v1/ecommerce/tracking/{job_keys}orders:readGet tracking with event history

Order payloads carry an ecommerce_type (e.g. shopify) plus order/line/address details — see the OpenAPI spec and Postman collection (Section 10) for the full schema.

Inventory

MethodPathScopeDescription
GET/api/v1/inventory/productsinventory:readList products (search, active_only, limit, offset)
POST/api/v1/inventory/productsinventory:writeCreate a product (sku, name required)
GET/api/v1/inventory/stockinventory:readStock levels (view=levels) or movements (view=movements)
POST/api/v1/inventory/stockinventory:writeaction: receive | adjust | write_off (product_id or sku, quantity; notes required for adjust/write_off)
GET/api/v1/inventory/inbound-ordersinventory:readList inbound goods orders (id, status filters)
POST/api/v1/inventory/inbound-ordersinventory:writeaction: create | receive_line | confirm

All Role 2 endpoints are account-scoped: the account is taken from the OAuth client, so a client can only see/affect its own account's data. A client with no associated account receives 403.

Example — create a product:

curl -X POST https://online.easyparcel.co.nz/api/v1/inventory/products \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "sku": "WIDGET-01", "name": "Blue Widget", "unit_weight": 0.25 }'

9. Quick-start checklist for an external app

  1. Obtain a client_id + client_secret from an administrator, scoped to what you need (least privilege).
  2. Note your tenant/environment base URL.
  3. On startup (and when the cached token nears expiry): POST /api/v1/oauth/token → cache the access_token for ~1 hour.
  4. Send Authorization: Bearer <token> on every API call.
  5. Handle 401 (token expired/revoked → re-request a token) and 429 (back off using X-RateLimit-Reset).
  6. Keep the client_secret in a secret manager. Rotate by revoking and recreating the client.

10. Resources

These are served by the platform (relative to the base URL):

  • API Quick Start Guide/api-quickstart.md
  • Postman Collection (eCommerce)/api/docs/postman-ecommerce-collection
  • OpenAPI 3.0 Specification (eCommerce)/api/docs/openapi-ecommerce
  • Postman Collection (Customer Accounts)/api/docs/postman-collection
  • In-app docs: Platform Integration (/settings/api-platform-integration, system_admin) and Customer API Management (/administration/api-management, customer_admin).

11. Appendix — token endpoint discovery

GET /api/v1/oauth/token (no auth) returns machine-readable metadata, including the live supported_scopes list:

{
  "endpoint": "/api/v1/oauth/token",
  "description": "OAuth 2.0 Token Endpoint - Client Credentials Grant",
  "supported_grant_types": ["client_credentials"],
  "supported_scopes": [
    "addresses:read", "addresses:write", "addresses:validate",
    "customers:read", "customers:write",
    "orders:read", "orders:write",
    "inventory:read", "inventory:write",
    "auth:sso"
  ]
}