Home / Integrations / API docs

API docs

Quote 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.

Get an API keyIntegrations statement

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

FieldTypeRequiredNotes
origin_zipstringyes5-digit US ZIP
origin_statestringyes2-letter state code
dest_zipstringyes5-digit US ZIP
dest_statestringyes2-letter state code
equipmentstringnoLTL, FTL, Intermodal, Drayage, Flatbed; default LTL
weight_lbnumberyes1–45,000
palletsnumberno0–30
piecesnumberno0–2,000
freight_classstringyese.g. 70, 92.5, 150, 300
accessorialsstring[]noliftgate, residential, appointment, inside, hazmat, temperature, flatbed_rigging
pickup_datestringnoYYYY-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

StatusMeaningBody
200Estimate returned{"ok":true,"quote":{...}}
400Validation failed{"ok":false,"error":"...","errors":[...]}
401Missing, invalid or revoked API key{"ok":false,"error":"unauthorized"}
429Rate limit — slow down{"ok":false,"error":"Too many requests"}
502ZIP 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.

Open my 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.

Integration question?

A human answers within two business days.

[email protected]

Quote first, integrate second

The hosted quote form and the API use the same estimate model. Start with the lane you have.

Get an instant quote