Home / Integrations / API docs
API docsQuote API v1
One endpoint, server-to-server, authenticated with a shipper API key. It returns the same planning range the hosted quote form shows — mileage, transit time and the basis — and nothing else. It does not create quotes, tenders or invoices.
Authentication
Create a key on your shipper account page (/account → API keys). The full key is shown once; only a SHA-256 hash is stored. Send it as a bearer token:
Authorization: Bearer plk_...
Keys are scoped to the shipper account that created them and can be revoked at any time. Revocation is immediate. Do not put a key in browser JavaScript or a public repository — the endpoint is server-to-server.
POST /api/v1/quote
Returns a planning estimate. Authenticated, rate limited and logged against the key.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
origin_zip | string | yes | 5-digit US ZIP |
origin_state | string | yes | 2-letter state code |
dest_zip | string | yes | 5-digit US ZIP |
dest_state | string | yes | 2-letter state code |
equipment | string | no | LTL, FTL, Intermodal, Drayage, Flatbed; default LTL |
weight_lb | number | yes | 1–45,000 |
pallets | number | no | 0–30 |
pieces | number | no | 0–2,000 |
freight_class | string | yes | e.g. 70, 92.5, 150, 300 |
accessorials | string[] | no | liftgate, residential, appointment, inside, hazmat, temperature, flatbed_rigging |
pickup_date | string | no | YYYY-MM-DD (accepted but not used in the estimate) |
Example request
POST https://portlandialogistics.com/api/v1/quote
Authorization: Bearer plk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"origin_zip": "40203",
"origin_state": "KY",
"dest_zip": "30301",
"dest_state": "GA",
"equipment": "LTL",
"weight_lb": 1200,
"pallets": 2,
"pieces": 2,
"freight_class": "70",
"accessorials": ["liftgate"],
"pickup_date": "2026-11-12"
}
Example response
{
"ok": true,
"quote": {
"origin_zip": "40203",
"dest_zip": "30301",
"equipment": "LTL",
"miles": 366,
"transit_days": 1,
"estimate_low_cents": 60500,
"estimate_high_cents": 76500,
"estimate_label": "$605 – $765",
"approximate": false,
"basis": "LTL estimate: $145 base + $0.62/mi x 366 + ...",
"fuel_cents": 0,
"accessorial_cents": 9500
},
"rate_index_basis": "Planning range midpoint / planning road miles; this API returns an estimate, not a booked rate.",
"request_id": "req_..."
}
Amounts are integer cents. basis is the human-readable calculation basis; print it next to any number you show a customer.
Errors and rate limits
| Status | Meaning | Body |
|---|---|---|
| 200 | Estimate returned | {"ok":true,"quote":{...}} |
| 400 | Validation failed | {"ok":false,"error":"...","errors":[...]} |
| 401 | Missing, invalid or revoked API key | {"ok":false,"error":"unauthorized"} |
| 429 | Rate limit — slow down | {"ok":false,"error":"Too many requests"} |
| 502 | ZIP could not be resolved; call dispatch | {"ok":false,"error":"..."} |
Limits: 120 requests per hour per API key and a per-connection cap on the public estimate. A rate limit is a 429 with no partial data — retry with backoff.
What the API does not do
- It does not create a quote record, book a load or issue an invoice.
- It does not accept billing or personal data — it prices a lane, nothing else.
- It does not return carrier names, capacity or a firm rate; dispatch confirms those in writing.
- There is no webhook or status subscription. Load status lives on the hosted tracking page.
Need a booking or status integration? See the integrations statement — it is scoped as a project, not claimed as available.
Create a key
Request the private account link by email, then open API keys in the account.
Security
- Keys are stored as SHA-256 hashes only.
- Revocation is immediate.
- Requests are counted per key so you can spot unexpected use.
- Never embed a key in client-side code.
Quote first, integrate second
The hosted quote form and the API use the same estimate model. Start with the lane you have.