T39 Customer API integration guide
Customer API integration guide

How Fernbank Logistics' engineering team goes from sandbox keys to a live production integration with Relay API, with the code you need at each step. Prepared by your solutions engineer, Devon Marsh.
Sample template: the firm, people, clients and figures are fictional and shown for illustration only.
Get connected
Step 1: Authenticate
Exchange your client ID and secret for an access token. Tokens expire after 3600 seconds, so cache and refresh them rather than requesting a new one per call.
curl -X POST https://sandbox.relayapi.example/v2/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=fernbank_sandbox_8841" \
-d "client_secret=REPLACE_WITH_YOUR_SECRET"
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Step 2: Call the shipments endpoint
import requests
headers = {"Authorization": "Bearer " + access_token}
resp = requests.get(
"https://sandbox.relayapi.example/v2/shipments?status=in_transit",
headers=headers,
)
print(resp.json())
Same request, your language
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://sandbox.relayapi.example/v2/shipments/SHP-88213"
resp = requests.get(
f"{BASE_URL}/shipments/SHP-88213",
headers={"Authorization": f"Bearer {access_token}"},
)
shipment = resp.json()
{
"shipment_id": "SHP-88213",
"status": "in_transit",
"carrier": "Northline Freight",
"eta": "2026-09-25T14:00:00Z",
"origin": "Columbus, OH",
"destination": "Reno, NV"
}
Core endpoints
| Method | Endpoint | Purpose | Auth scope |
|---|---|---|---|
| POST | /oauth/token | Exchange credentials for an access token | none |
| GET | /shipments | List shipments, filter by status or date | shipments |
| GET | /shipments/{id} | Get one shipment's current detail | shipments |
| POST | /shipments | Create a new shipment | shipments |
| POST | /webhooks | Register a webhook endpoint | webhooks |
| GET | /rate-limit | Check your current usage against the limit | none |
Webhook flow for status updates
sequenceDiagram
participant App as Fernbank app
participant Relay as Relay API
participant Carrier as Carrier network
App->>Relay: POST /oauth/token (client credentials)
Relay-->>App: access_token, expires_in 3600
App->>Relay: POST /webhooks (register endpoint)
Relay-->>App: 201 Created
Carrier->>Relay: Status update (in transit to delivered)
Relay->>App: POST to your webhook (signed payload)
App-->>Relay: 200 OK within 5 seconds
Relay retries a webhook delivery up to 5 times with exponential backoff if your endpoint does not return a 200 within 5 seconds. Verify the X-Relay-Signature header on every payload before trusting it.
Common error codes
| Code | Meaning | Typical cause | What to do |
|---|---|---|---|
| 401 | Unauthorized | Expired or missing access token | Refresh the token, then retry |
| 403 | Forbidden | Token missing the required scope | Request the scope from your solutions engineer |
| 404 | Not found | Shipment ID does not exist in this environment | Confirm you are calling sandbox versus production |
| 429 | Rate limited | Over 600 requests per minute | Back off and retry using the Retry-After header |
| 500 | Server error | Relay-side issue | Retry with backoff; contact support if it persists |
Never call production with sandbox credentials, or the reverse
Sandbox and production use separate client IDs, secrets and base URLs. A sandbox credential against the production URL returns 401 and will not silently fall back. Store both sets of credentials in separate secrets, never hardcoded in source.
Go-live checklist
Production keys are issued after a technical review
Devon schedules a 30-minute technical review once the checklist above is complete. Production credentials are issued the same day the review passes. Plan for this before setting a hard go-live date with your own stakeholders.
Next steps
- Finish webhook signature verification and confirm it rejects unsigned payloads.
- Book the technical review with Devon Marsh once the checklist is complete.
- Run the load test and share results before the Oct 3 target go-live.
How to use this template
Duplicate this page per customer integration. Replace the values below from the integration record and your API dashboard; if a value like a target date is not yet agreed, write Unavailable rather than guessing.
| Variable | Meaning | Required |
|---|---|---|
{{customer.name}} | Customer integrating with the API | Required |
{{integration.sandbox_url}} | Sandbox base URL for this integration | Required |
{{integration.production_url}} | Production base URL | Required |
{{integration.solutions_engineer}} | Solutions engineer supporting the integration | Required |
{{integration.golive_date}} | Target production go-live date | Optional |
{{integration.client_id_sandbox}} | Sandbox client ID issued to this customer | Required |
Never place a live client secret in this document; reference where the customer retrieves it from their own secrets vault.