Ship24 Tracking API integration
Opinionated wizard: detect the project, clarify the plan, guide the implementation, verify the result.
Facts in this skill come from docs.ship24.com and the OpenAPI 3.1 spec at
docs.ship24.com/assets/openapi/ship24-tracking-api.yaml.
The bundled references/endpoints.md and schemas.md are generated from that spec, errors.md and
rate-limits.md from the docs pages. When a detail matters (a field name, a status
code, a limit), read the reference file rather than guessing:
| Need |
Read |
| Every endpoint, parameters, responses, rate limit |
references/endpoints.md |
| Field tables for tracker, tracking, shipment, event, request bodies |
references/schemas.md |
| HTTP statuses and error codes |
references/errors.md |
| Rate limits and rate-limit headers |
references/rate-limits.md |
| Webhook vs polling trade-offs, matching records, idempotent creation |
references/integration-patterns.md |
Sibling skills: ship24-webhooks (build the receiver), ship24-couriers (courier codes and required fields),
ship24-tracking-statuses (map statuses to business logic), ship24-troubleshooting (errors), ship24-node-sdk
(the official ship24 npm package), ship24-track (look up one parcel now via the MCP tools).
Phase 1: Detect
Scan before asking. Use the host tool's search if there is no shell.
grep -ri "ship24" . --include=*.{js,ts,py,rb,php,go,java,cs,env,json,yaml,yml} 2>/dev/null | head -20
grep -ri "SHIP24" .env .env.local .env.example 2>/dev/null
grep -rE "express|fastify|nest|fastapi|django|flask|rails|laravel|symfony|gin|spring|aspnet" \
package.json requirements.txt pyproject.toml Gemfile composer.json go.mod pom.xml *.csproj 2>/dev/null | head
grep -rn "webhook" . --include=*.{js,ts,py,rb,php,go,java,cs} 2>/dev/null | head
grep -rE "bull|bullmq|celery|sidekiq|rabbitmq|sqs|pubsub|kafka" \
package.json requirements.txt pyproject.toml Gemfile composer.json go.mod 2>/dev/null | head -5
Note: existing Ship24 usage (trackers created? webhook route present?), language and framework, HTTP client in
use, queue or async infrastructure, an existing public webhook route that can be extended.
If the project is Node.js or TypeScript, prefer the official SDK (npm install ship24) over hand-written HTTP
calls and switch to the ship24-node-sdk skill for the implementation details.
Phase 2: Clarify
Ask only what detection could not determine, one question at a time.
- Account and key. Does the user have a Ship24 account and API key? A free plan exists for integration and
testing: dashboard.ship24.com/onboarding. Keys live under
Integrations → API Keys (up to 20 active keys per account).
- Plan type. Per-shipment (the standard product: trackers, webhooks, polling) or per-call (a separate
subscription, one synchronous endpoint, no trackers)? If unsure: per-shipment is the default choice; Ship24
documents it as offering more features, faster fetching and lower overall cost.
- Update mechanism (per-shipment only). Webhooks (Ship24 pushes updates, recommended) or polling (the app
calls the API)? Webhooks need a public HTTPS endpoint. At thousands of trackers, polling stops being
practical.
- Constraints. New project or existing app? Serverless? No public URL? Multi-tenant?
Do not write code until plan type, key handling (env var) and update mechanism are settled.
Phase 3: Guide
Authentication and base URL (all plans)
- Base URL:
https://api.ship24.com/public/v1
- Header on every request:
Authorization: Bearer <api key> (keys look like apik_...).
- Read the key from the
SHIP24_API_KEY environment variable. Never hardcode it, commit it, or ship it to a
browser or mobile client: the key grants full access to the account's tracking data and quota.
# .env (never committed)
SHIP24_API_KEY=apik_your_key_here
Route by plan
| Plan |
Endpoints |
Docs |
| Per-shipment |
POST /trackers, POST /trackers/bulk, POST /trackers/track, GET /trackers/{trackerId}/results, webhooks |
Trackers, Webhooks |
| Per-call |
POST /tracking/search only |
Per-call API |
Per-shipment plan
Webhook flow (recommended)
- Build a
POST endpoint that answers 2xx quickly and processes asynchronously (see ship24-webhooks).
- Register its URL in the dashboard: Integrations → Webhooks. There is no API for this.
- Create a tracker per shipment with
POST /trackers (or up to 100 at once with POST /trackers/bulk).
Store the returned trackerId.
- Ship24 pushes every new event to the endpoint. A webhook
events[] array contains only the events
discovered since the last push; API responses contain the full history.
Polling flow
POST /trackers, store trackerId.
- Do not poll immediately: results are not available the instant a tracker is created.
GET /trackers/{trackerId}/results on an interval the app chooses. Ship24 does not mandate a cadence.
Longer intervals for slower shipments.
- Stop when the tracker's
isTracked is false: Ship24 has stopped tracking that shipment.
Synchronous one-shot flow
POST /trackers/track creates the tracker and returns results in the same call. The first call can take up to
one minute because Ship24 queries couriers synchronously; some couriers do not support it and return results on
later calls. Calling it again with the same payload returns the same tracker with fresh results.
Idempotency (verified in Ship24's implementation, see the plugin's CONTRIBUTING). POST /trackers and
POST /trackers/track look up the account's trackers with the same clientTrackerId when one is sent, otherwise
with the same trackingNumber, and reuse a candidate without consuming quota when shippingDate (day
precision), originCountryCode, destinationCountryCode, destinationPostCode, shipmentReference,
courierName, trackingUrl, settings.restrictTrackingToCourierCode and the courierCode list all match.
Otherwise a new tracker is created. tracker_conflict comes back when the clientTrackerId belongs to an active
tracker with a different payload, and on POST /trackers/track when the courierCode list overlaps an existing
tracker's list without matching it; differentiate with shippingDate or destinationCountryCode, or resend the
same payload.
Identify shipments. Tracking numbers are not unique across couriers or time. Store Ship24's trackerId.
Alternatively set clientTrackerId to the app's own unique id: Ship24 validates its uniqueness across
subscribed trackers and most endpoints accept ?searchBy=clientTrackerId. shipmentReference is free text,
not validated for uniqueness, and returned in responses and webhooks.
Tracker creation fields (full table in references/schemas.md, tracker-create-request):
| Field |
Notes |
trackingNumber |
Required. 5 to 50 chars, A-Z a-z 0-9 - _ / .; returned uppercased, compare case-insensitively. Dummy values such as 123456789 are rejected. |
courierCode |
Optional string or array (max 3 per request). Improves detection; see ship24-couriers. |
clientTrackerId |
Optional, max 100 chars, unique across subscribed trackers. |
shipmentReference |
Optional, max 100 chars, not unique. |
originCountryCode, destinationCountryCode |
ISO 3166-1 alpha-2 or alpha-3 accepted; alpha-2 returned. Some couriers require the destination. |
destinationPostCode |
1 to 32 chars. Some couriers require it. |
shippingDate |
ISO 8601. Rejected with shipping_date_outdated when older than 180 days. |
recipient.email, recipient.name |
Used for email notifications; name also feeds courier-restricted tracking data. Max 254 and 100 chars. |
settings.restrictTrackingToCourierCode |
Track only with the given courier codes. |
Bulk creation. POST /trackers/bulk accepts 1 to 100 items and answers 201 (the docs also list 200),
207 (partial) or an error. Its body is { status, summary, data, error } with status in
success | partial | error, unlike the { data } envelope of the other tracker endpoints. Rate limit 3 requests
per second.
Per-call plan
POST /tracking/search with the tracking number (plus optional courier and destination hints) fetches results
synchronously from couriers. Response time can reach one minute. No tracker is created, nothing to store, no
webhooks. Requires an active per-call subscription; without one the API answers no_active_subscription.
Data handling rules that bite
- Add-on fields (for example
shipment.delivery.aiPredictiveDeliveryDate) are absent, not null, when
the account lacks the option or Ship24 has no value. Check presence before reading.
- Event
occurrenceDatetime is a logistic-date-time: local time, local time with offset, UTC, or date only.
Keep it as a string; use the event order field to sort events that lack a time.
datetime, utcOffset, hasNoTime (events) and signedBy (delivery) are deprecated. Do not build on them.
- Rate limits are per endpoint per second (most are 10 req/s, bulk 3, couriers and resend 1). Honor
Retry-After on 429. Details in references/rate-limits.md.
- Error bodies are
{ "errors": [{ "code", "message" }], "data": null } (message optional), except the bulk
endpoint's { status, summary, data, error }. Branch on code, not on the message text. Codes in
references/errors.md.
Phase 4: Verify
Key links
1---2name: ship24-integration3description: Integrate the Ship24 Tracking API into a codebase end to end. Use when asked to "add Ship24", "integrate Ship24", "integrate package, parcel or shipment tracking", create trackers, choose between webhooks and polling, or call the per-call tracking endpoint, in any language or framework. Detects the project's stack, confirms the Ship24 plan type and update mechanism, then guides implementation and verification against the OpenAPI spec. For webhook receivers, courier codes, statuses, errors or the Node SDK, the sibling ship24-* skills go deeper.4license: MIT5---67# Ship24 Tracking API integration89Opinionated wizard: detect the project, clarify the plan, guide the implementation, verify the result.1011Facts in this skill come from [docs.ship24.com](https://docs.ship24.com) and the OpenAPI 3.1 spec at12[docs.ship24.com/assets/openapi/ship24-tracking-api.yaml](https://docs.ship24.com/assets/openapi/ship24-tracking-api.yaml).13The bundled `references/endpoints.md` and `schemas.md` are generated from that spec, `errors.md` and14`rate-limits.md` from the docs pages. When a detail matters (a field name, a status15code, a limit), read the reference file rather than guessing:1617| Need | Read |18| --- | --- |19| Every endpoint, parameters, responses, rate limit | `references/endpoints.md` |20| Field tables for tracker, tracking, shipment, event, request bodies | `references/schemas.md` |21| HTTP statuses and error codes | `references/errors.md` |22| Rate limits and rate-limit headers | `references/rate-limits.md` |23| Webhook vs polling trade-offs, matching records, idempotent creation | `references/integration-patterns.md` |2425Sibling skills: `ship24-webhooks` (build the receiver), `ship24-couriers` (courier codes and required fields),26`ship24-tracking-statuses` (map statuses to business logic), `ship24-troubleshooting` (errors), `ship24-node-sdk`27(the official `ship24` npm package), `ship24-track` (look up one parcel now via the MCP tools).2829## Phase 1: Detect3031Scan before asking. Use the host tool's search if there is no shell.3233```bash34grep -ri "ship24" . --include=*.{js,ts,py,rb,php,go,java,cs,env,json,yaml,yml} 2>/dev/null | head -2035grep -ri "SHIP24" .env .env.local .env.example 2>/dev/null36grep -rE "express|fastify|nest|fastapi|django|flask|rails|laravel|symfony|gin|spring|aspnet" \37 package.json requirements.txt pyproject.toml Gemfile composer.json go.mod pom.xml *.csproj 2>/dev/null | head38grep -rn "webhook" . --include=*.{js,ts,py,rb,php,go,java,cs} 2>/dev/null | head39grep -rE "bull|bullmq|celery|sidekiq|rabbitmq|sqs|pubsub|kafka" \40 package.json requirements.txt pyproject.toml Gemfile composer.json go.mod 2>/dev/null | head -541```4243Note: existing Ship24 usage (trackers created? webhook route present?), language and framework, HTTP client in44use, queue or async infrastructure, an existing public webhook route that can be extended.4546If the project is Node.js or TypeScript, prefer the official SDK (`npm install ship24`) over hand-written HTTP47calls and switch to the `ship24-node-sdk` skill for the implementation details.4849## Phase 2: Clarify5051Ask only what detection could not determine, one question at a time.52531. **Account and key.** Does the user have a Ship24 account and API key? A free plan exists for integration and54 testing: [dashboard.ship24.com/onboarding](https://dashboard.ship24.com/onboarding). Keys live under55 Integrations → API Keys (up to 20 active keys per account).562. **Plan type.** Per-shipment (the standard product: trackers, webhooks, polling) or per-call (a separate57 subscription, one synchronous endpoint, no trackers)? If unsure: per-shipment is the default choice; Ship2458 documents it as offering more features, faster fetching and lower overall cost.593. **Update mechanism** (per-shipment only). Webhooks (Ship24 pushes updates, recommended) or polling (the app60 calls the API)? Webhooks need a public HTTPS endpoint. At thousands of trackers, polling stops being61 practical.624. **Constraints.** New project or existing app? Serverless? No public URL? Multi-tenant?6364Do not write code until plan type, key handling (env var) and update mechanism are settled.6566## Phase 3: Guide6768### Authentication and base URL (all plans)6970- Base URL: `https://api.ship24.com/public/v1`71- Header on every request: `Authorization: Bearer <api key>` (keys look like `apik_...`).72- Read the key from the `SHIP24_API_KEY` environment variable. Never hardcode it, commit it, or ship it to a73 browser or mobile client: the key grants full access to the account's tracking data and quota.7475```bash76# .env (never committed)77SHIP24_API_KEY=apik_your_key_here78```7980### Route by plan8182| Plan | Endpoints | Docs |83| --- | --- | --- |84| Per-shipment | `POST /trackers`, `POST /trackers/bulk`, `POST /trackers/track`, `GET /trackers/{trackerId}/results`, webhooks | [Trackers](https://docs.ship24.com/trackers), [Webhooks](https://docs.ship24.com/webhooks/overview) |85| Per-call | `POST /tracking/search` only | [Per-call API](https://docs.ship24.com/per-call-api) |8687### Per-shipment plan8889**Webhook flow (recommended)**90911. Build a `POST` endpoint that answers `2xx` quickly and processes asynchronously (see `ship24-webhooks`).922. Register its URL in the dashboard: Integrations → Webhooks. There is no API for this.933. Create a tracker per shipment with `POST /trackers` (or up to 100 at once with `POST /trackers/bulk`).94 Store the returned `trackerId`.954. Ship24 pushes every new event to the endpoint. A webhook `events[]` array contains only the events96 discovered since the last push; API responses contain the full history.9798**Polling flow**991001. `POST /trackers`, store `trackerId`.1012. Do not poll immediately: results are not available the instant a tracker is created.1023. `GET /trackers/{trackerId}/results` on an interval the app chooses. Ship24 does not mandate a cadence.103 Longer intervals for slower shipments.1044. Stop when the tracker's `isTracked` is `false`: Ship24 has stopped tracking that shipment.105106**Synchronous one-shot flow**107108`POST /trackers/track` creates the tracker and returns results in the same call. The first call can take up to109one minute because Ship24 queries couriers synchronously; some couriers do not support it and return results on110later calls. Calling it again with the same payload returns the same tracker with fresh results.111112**Idempotency** (verified in Ship24's implementation, see the plugin's CONTRIBUTING). `POST /trackers` and113`POST /trackers/track` look up the account's trackers with the same `clientTrackerId` when one is sent, otherwise114with the same `trackingNumber`, and reuse a candidate without consuming quota when `shippingDate` (day115precision), `originCountryCode`, `destinationCountryCode`, `destinationPostCode`, `shipmentReference`,116`courierName`, `trackingUrl`, `settings.restrictTrackingToCourierCode` and the `courierCode` list all match.117Otherwise a new tracker is created. `tracker_conflict` comes back when the `clientTrackerId` belongs to an active118tracker with a different payload, and on `POST /trackers/track` when the `courierCode` list overlaps an existing119tracker's list without matching it; differentiate with `shippingDate` or `destinationCountryCode`, or resend the120same payload.121122**Identify shipments.** Tracking numbers are not unique across couriers or time. Store Ship24's `trackerId`.123Alternatively set `clientTrackerId` to the app's own unique id: Ship24 validates its uniqueness across124subscribed trackers and most endpoints accept `?searchBy=clientTrackerId`. `shipmentReference` is free text,125not validated for uniqueness, and returned in responses and webhooks.126127**Tracker creation fields** (full table in `references/schemas.md`, `tracker-create-request`):128129| Field | Notes |130| --- | --- |131| `trackingNumber` | Required. 5 to 50 chars, `A-Z a-z 0-9 - _ / .`; returned uppercased, compare case-insensitively. Dummy values such as `123456789` are rejected. |132| `courierCode` | Optional string or array (max 3 per request). Improves detection; see `ship24-couriers`. |133| `clientTrackerId` | Optional, max 100 chars, unique across subscribed trackers. |134| `shipmentReference` | Optional, max 100 chars, not unique. |135| `originCountryCode`, `destinationCountryCode` | ISO 3166-1 alpha-2 or alpha-3 accepted; alpha-2 returned. Some couriers require the destination. |136| `destinationPostCode` | 1 to 32 chars. Some couriers require it. |137| `shippingDate` | ISO 8601. Rejected with `shipping_date_outdated` when older than 180 days. |138| `recipient.email`, `recipient.name` | Used for email notifications; `name` also feeds courier-restricted tracking data. Max 254 and 100 chars. |139| `settings.restrictTrackingToCourierCode` | Track only with the given courier codes. |140141**Bulk creation.** `POST /trackers/bulk` accepts 1 to 100 items and answers `201` (the docs also list `200`),142`207` (partial) or an error. Its body is `{ status, summary, data, error }` with `status` in143`success | partial | error`, unlike the `{ data }` envelope of the other tracker endpoints. Rate limit 3 requests144per second.145146### Per-call plan147148`POST /tracking/search` with the tracking number (plus optional courier and destination hints) fetches results149synchronously from couriers. Response time can reach one minute. No tracker is created, nothing to store, no150webhooks. Requires an active per-call subscription; without one the API answers `no_active_subscription`.151152### Data handling rules that bite153154- Add-on fields (for example `shipment.delivery.aiPredictiveDeliveryDate`) are **absent**, not `null`, when155 the account lacks the option or Ship24 has no value. Check presence before reading.156- Event `occurrenceDatetime` is a `logistic-date-time`: local time, local time with offset, UTC, or date only.157 Keep it as a string; use the event `order` field to sort events that lack a time.158- `datetime`, `utcOffset`, `hasNoTime` (events) and `signedBy` (delivery) are deprecated. Do not build on them.159- Rate limits are per endpoint per second (most are 10 req/s, bulk 3, couriers and resend 1). Honor160 `Retry-After` on `429`. Details in `references/rate-limits.md`.161- Error bodies are `{ "errors": [{ "code", "message" }], "data": null }` (`message` optional), except the bulk162 endpoint's `{ status, summary, data, error }`. Branch on `code`, not on the message text. Codes in163 `references/errors.md`.164165## Phase 4: Verify166167- [ ] API key read from `SHIP24_API_KEY`; not in source, not in client-side code.168- [ ] `trackerId` (or a validated `clientTrackerId`) persisted for every created tracker.169- [ ] Webhook endpoint answers `2xx` before processing and matches payloads on `trackerId` or `clientTrackerId`.170- [ ] Duplicate deliveries handled: deduplicate on `events[].eventId`.171- [ ] Polling does not start immediately, uses an interval the team chose deliberately, and stops on172 `isTracked: false`.173- [ ] `401`, `403`, `422`, `429` (with `Retry-After`) and `5xx` handled; error `code` logged.174- [ ] Add-on fields read only when present.175- [ ] Tested end to end with a Ship24 sample tracking number such as `SHIP24_SAMPLE_DELIVERED_000`176 (see `ship24-tracking-statuses` for the full list).177178## Key links179180| Resource | URL |181| --- | --- |182| Dashboard, API keys, webhook URL | https://dashboard.ship24.com |183| Documentation | https://docs.ship24.com |184| API reference | https://docs.ship24.com/tracking-api-reference/ |185| OpenAPI 3.1 spec | https://docs.ship24.com/assets/openapi/ship24-tracking-api.yaml |186| Node SDK | https://github.com/ship24/ship24-node |187| Pricing | https://www.ship24.com/pricing |