Developer API v1

Twyft Developer API

Limited access is available only to active Business Accounts after separate enablement by Twyft. Contact support before building a production integration.

Manage API keys

Authentication and limits

Send Authorization: Bearer twft_…. Dashboard cookies and sessions never authenticate /api/v1. A key secret is displayed once, stored only as a SHA-256 hash, and stops working immediately when revoked. Resources are isolated to the key owner; a missing or another owner’s shipment has the same 404 response.

Each active key has its own rolling 60-second limit: 120 requests for standard keys or 600 requests for approved high-volume keys. Read X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and on 429 Retry-After.

Published surface

The only accepted service slugs are iom-nextday and iom-uk-standard. iom-nextday is IM→IM; iom-uk-standard is IM→ordinary GB. Northern Ireland, Channel Islands, IM-as-GB and other international creation are unavailable.

POST /quotesGET /shipmentsPOST /shipmentsGET /shipments/{ref}GET /shipments/{ref}/label
  • Quotes return an available price in integer GBP pence or a truthful unavailable outcome, without creating effects.
  • Lists use limit 1–100 (default 50) and zero-based offset, with optional status and clientRef.
  • Creation requires Idempotency-Key. Omitted/false confirm creates a draft. True requests prepaid or separately approved account-credit confirmation and may return confirmed, credit hold, insufficient balance, or confirmation unavailable.
  • Replay the identical validated request and key to receive the same result without duplicate shipment, financial, provider, or operational effects. Different input with that key returns 409.
  • Poll owned shipment detail for status. Download a PDF only when labelAvailable is true; otherwise label retrieval returns 409 label_not_ready.

Requests and constraints

POST /quotes

FieldPresenceConstraint
serviceRequirediom-nextday or iom-uk-standard
originRequiredcountry must be IM; non-empty postcode
destinationRequireduppercase two-letter country and non-empty postcode; must match service route
parcelRequiredfinite numbers: weightKg ≥ 0.01; dimensions ≥ 0.1 cm; contents optional
collectionDateOptionalreal YYYY-MM-DD calendar date

POST /shipments

FieldPresenceConstraint
Idempotency-Key headerRequired1–255 characters; not whitespace-only
serviceRequirediom-nextday (IM→IM) or iom-uk-standard (IM→ordinary GB)
senderRequiredname, valid email, addressLine1, town, postcode, country=IM; phone/addressLine2 optional
recipientRequiredname, addressLine1, town, postcode and route country; email/phone/addressLine2 optional
parcelRequiredweight 0.01–25 kg; dimensions ≥ 0.1 cm; contents optional, max 500 characters
collectionDateOptionalreal YYYY-MM-DD; omitted resolves to next eligible IM collection date
confirmOptionalboolean; omitted is equivalent to false (draft)
clientRefOptionalmaximum 100 characters

If collectionDate is omitted, Twyft resolves the next eligible Isle of Man collection date. A supplied date must be a real calendar date; an unavailable date produces the documented unavailable/validation outcome rather than an unexplained substitution.

Before using confirm: true, choose Prepay Balance on Account → API Keys → Payment Method and fund the balance, or have Twyft approve account credit and select it there. Insufficient prepay returns 402; no usable approved funding returns 409. A credit hold is not a confirmed shipment. Saved-card execution is not supported.

GET /shipments

FieldPresenceConstraint
statusOptionalone published shipment status
clientRefOptionalexact client reference filter
limitOptionalinteger 1–100; default 50
offsetOptionalinteger ≥ 0; default 0

GET detail and label

FieldPresenceConstraint
refRequiredopaque path value, 1–30 characters

Responses and vocabulary

Shipment statuses: draft · credit_hold · pending · confirmed · collected · at_depot · in_transit · out_for_delivery · delivered · failed · cancelled · written_off

Quote responses distinguish available prices from unavailable reasons. Creation returns draft, confirmed with prepaid or account_credit funding, or credit hold. Representative contract responses:

{
  "quoteAvailable": {
    "outcome": "available",
    "service": "iom-nextday",
    "price": {
      "currency": "GBP",
      "amountPence": 1295
    }
  },
  "quoteUnavailable": {
    "outcome": "unavailable",
    "reasonCode": "route_unavailable",
    "message": "The selected route is unavailable."
  },
  "draft": {
    "outcome": "draft",
    "shipment": {
      "ref": "shp_demo_0001",
      "clientRef": "ORDER-DEMO-1",
      "service": "iom-nextday",
      "status": "draft",
      "createdAt": "2026-01-15T10:00:00Z",
      "sender": {
        "name": "Example Sender",
        "email": "sender@example.invalid",
        "addressLine1": "1 Example Street",
        "town": "Douglas",
        "postcode": "IM1 1AA",
        "country": "IM"
      },
      "recipient": {
        "name": "Example Recipient",
        "addressLine1": "2 Sample Road",
        "town": "Douglas",
        "postcode": "IM2 2BB",
        "country": "IM"
      },
      "parcel": {
        "weightKg": 1,
        "lengthCm": 20,
        "widthCm": 15,
        "heightCm": 10
      },
      "price": null,
      "tracking": [
        {
          "status": "pending",
          "occurredAt": "2026-01-15T10:00:00Z",
          "description": "Draft created"
        }
      ],
      "labelAvailable": false,
      "carrierRef": null
    }
  },
  "confirmedPrepaid": {
    "outcome": "confirmed",
    "funding": "prepaid",
    "shipment": {
      "ref": "shp_demo_0002",
      "clientRef": "ORDER-DEMO-2",
      "service": "iom-nextday",
      "status": "confirmed",
      "createdAt": "2026-01-15T10:05:00Z",
      "sender": {
        "name": "Example Sender",
        "email": "sender@example.invalid",
        "addressLine1": "1 Example Street",
        "town": "Douglas",
        "postcode": "IM1 1AA",
        "country": "IM"
      },
      "recipient": {
        "name": "Example Recipient",
        "addressLine1": "2 Sample Road",
        "town": "Douglas",
        "postcode": "IM2 2BB",
        "country": "IM"
      },
      "parcel": {
        "weightKg": 1,
        "lengthCm": 20,
        "widthCm": 15,
        "heightCm": 10
      },
      "price": {
        "currency": "GBP",
        "amountPence": 1295
      },
      "tracking": [
        {
          "status": "confirmed",
          "occurredAt": "2026-01-15T10:05:00Z",
          "description": "Booking confirmed"
        }
      ],
      "labelAvailable": false,
      "carrierRef": null
    }
  },
  "confirmedAccountCredit": {
    "outcome": "confirmed",
    "funding": "account_credit",
    "shipment": {
      "ref": "shp_demo_0002",
      "clientRef": "ORDER-DEMO-2",
      "service": "iom-nextday",
      "status": "confirmed",
      "createdAt": "2026-01-15T10:05:00Z",
      "sender": {
        "name": "Example Sender",
        "email": "sender@example.invalid",
        "addressLine1": "1 Example Street",
        "town": "Douglas",
        "postcode": "IM1 1AA",
        "country": "IM"
      },
      "recipient": {
        "name": "Example Recipient",
        "addressLine1": "2 Sample Road",
        "town": "Douglas",
        "postcode": "IM2 2BB",
        "country": "IM"
      },
      "parcel": {
        "weightKg": 1,
        "lengthCm": 20,
        "widthCm": 15,
        "heightCm": 10
      },
      "price": {
        "currency": "GBP",
        "amountPence": 1295
      },
      "tracking": [
        {
          "status": "confirmed",
          "occurredAt": "2026-01-15T10:05:00Z",
          "description": "Booking confirmed"
        }
      ],
      "labelAvailable": false,
      "carrierRef": null
    }
  },
  "creditHold": {
    "outcome": "credit_hold",
    "shipment": {
      "ref": "shp_demo_0003",
      "clientRef": "ORDER-DEMO-3",
      "service": "iom-nextday",
      "status": "credit_hold",
      "createdAt": "2026-01-15T10:10:00Z",
      "sender": {
        "name": "Example Sender",
        "email": "sender@example.invalid",
        "addressLine1": "1 Example Street",
        "town": "Douglas",
        "postcode": "IM1 1AA",
        "country": "IM"
      },
      "recipient": {
        "name": "Example Recipient",
        "addressLine1": "2 Sample Road",
        "town": "Douglas",
        "postcode": "IM2 2BB",
        "country": "IM"
      },
      "parcel": {
        "weightKg": 1,
        "lengthCm": 20,
        "widthCm": 15,
        "heightCm": 10
      },
      "price": null,
      "tracking": [],
      "labelAvailable": false,
      "carrierRef": null
    }
  }
}

Executable end-to-end example

The creation response supplies SHIPMENT_REF for every later command. A confirmed request requires a funded prepay balance or separately approved account credit.

#!/usr/bin/env bash
set -euo pipefail
export BASE_URL="https://twyft.im/api/v1" # use https://shadow.twyft.im/api/v1 for acceptance
export API_KEY="twft_replace_with_the_once_displayed_secret"

curl --fail-with-body -sS "$BASE_URL/quotes" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"service":"iom-nextday","origin":{"country":"IM","postcode":"IM1 1AA"},"destination":{"country":"IM","postcode":"IM2 2BB"},"parcel":{"weightKg":1,"lengthCm":20,"widthCm":15,"heightCm":10}}' | jq .
curl --fail-with-body -sS "$BASE_URL/quotes" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"service":"iom-uk-standard","origin":{"country":"IM","postcode":"IM1 1AA"},"destination":{"country":"GB","postcode":"M1 1AE"},"parcel":{"weightKg":1,"lengthCm":20,"widthCm":15,"heightCm":10}}' | jq .

export IDEMPOTENCY_KEY="order-demo-001"
export CREATE_BODY='{"service":"iom-nextday","sender":{"name":"Example Sender","email":"sender@example.com","addressLine1":"1 Example Street","town":"Douglas","postcode":"IM1 1AA","country":"IM"},"recipient":{"name":"Example Recipient","addressLine1":"2 Sample Road","town":"Douglas","postcode":"IM2 2BB","country":"IM"},"parcel":{"weightKg":1,"lengthCm":20,"widthCm":15,"heightCm":10,"contents":"Documents"},"clientRef":"ORDER-DEMO-001","confirm":true}'
CREATE_RESPONSE=$(curl --fail-with-body -sS "$BASE_URL/shipments" \
  -H "Authorization: Bearer $API_KEY" -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" -d "$CREATE_BODY")
printf '%s\n' "$CREATE_RESPONSE" | jq .
SHIPMENT_REF=$(printf '%s' "$CREATE_RESPONSE" | jq -er '.shipment.ref | select(type == "string" and length > 0)')
export SHIPMENT_REF

# Identical replay preserves the exact body and key.
curl --fail-with-body -sS "$BASE_URL/shipments" \
  -H "Authorization: Bearer $API_KEY" -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" -d "$CREATE_BODY" | jq .
curl --fail-with-body -sS "$BASE_URL/shipments?limit=50&offset=0" -H "Authorization: Bearer $API_KEY" | jq .
DETAIL_RESPONSE=$(curl --fail-with-body -sS "$BASE_URL/shipments/$SHIPMENT_REF" -H "Authorization: Bearer $API_KEY")
printf '%s\n' "$DETAIL_RESPONSE" | jq .

LABEL_AVAILABLE=$(printf '%s' "$DETAIL_RESPONSE" | jq -r 'if (.labelAvailable | type) == "boolean" then .labelAvailable else error("labelAvailable must be a boolean") end')
if [[ "$LABEL_AVAILABLE" == "true" ]]; then
  curl --fail-with-body -sS "$BASE_URL/shipments/$SHIPMENT_REF/label" \
    -H "Authorization: Bearer $API_KEY" -o "$SHIPMENT_REF.pdf"
else
  printf 'PENDING: label is not yet available for %s\n' "$SHIPMENT_REF" >&2
fi

Errors and availability

Errors use { "error": { "code": string, "message": string, "field"?: string, "details"?: object } }.

Exact codes: invalid_request · authentication_required · forbidden · shipment_not_found · label_not_ready · idempotency_conflict · confirmation_unavailable · insufficient_prepay_balance · validation_failed · rate_limited · service_unavailable · internal_error

400 malformed request; 401 missing, invalid or inactive credential; 402 insufficient prepay; 403 forbidden; 404 missing/non-owned shipment; 409 idempotency conflict, confirmation unavailable, or label not ready; 422 validation; 429 rate limit; 500 unexpected error; 503 required service unavailable.

Webhooks are not currently available. Poll shipment detail for status and use authenticated label retrieval only after labelAvailable becomes true.