API reference
Base URL https://api-production-dd08.up.railway.app/api. Machine-readable OpenAPI: docs-json · interactive explorer
The live OpenAPI document is unavailable right now, so this is the written reference.
Wripp IQ public API (v1)
The public API lets your systems and AI agents search the marketplace, read lane market ranges, post loads, and bid, using the same rules as the Wripp IQ web app.
- Base URL:
https://api-production-dd08.up.railway.app/api - OpenAPI:
GET /api/docs-json(reference UI at/api/docs). In production it lists only/api/public/*and/api/v1/*. - MCP server:
packages/mcp-serverexposessearch_loads,get_market_range,create_loadandsubmit_bidto agents such as Claude.
Authentication
Send an organization API key on every request:
x-api-key: wrpk_<prefix>_<secret>Authorization: Bearer wrpk_… also works. Every call acts as the organization that owns the key.
Managing keys
Keys are managed by a signed-in user whose org role has manage_team (owner or admin). These routes use the normal web session, not an API key.
| Method | Path | Notes |
|---|---|---|
GET | /api/developer/keys | Lists keys (masked), with scopes, limit, and rotation state |
POST | /api/developer/keys | { name, scopes?, rateLimitPerMinute? } → { id, name, key, scopes, rateLimitPerMinute }. The secret is shown once. |
POST | /api/developer/keys/:id/rotate | → { id, name, key, previousExpiresAt } |
DELETE | /api/developer/keys/:id | Revokes the key immediately |
Rotation. Rotating issues a new secret under the same prefix. The previous secret keeps working for 24 hours (previousExpiresAt), so you can roll deployments over. If you rotate again inside that window, the older secret stops working immediately. Only the current secret and the one before it are ever valid.
Every create, rotate and revoke is written to the org security log.
Scopes
Choose scopes when you create a key. If you omit scopes, the key gets loads:read, bookings:read and invoices:read. Keys created before scopes existed were backfilled to that same set.
| Scope | Grants |
|---|---|
loads:read | GET /v1/loads/search, GET /v1/loads/:id, GET /public/loads |
loads:write | POST /v1/loads |
bids:read | GET /v1/bids |
bids:write | POST /v1/loads/:loadId/bids |
market:read | GET /v1/market-range, GET /v1/loads/search, GET /v1/market/index (ELITE+ plan) |
bookings:read | GET /public/bookings |
invoices:read | Reserved for GET /public/invoices (that route currently accepts any valid key; ELITE+ plan) |
bids:autonomous | Lets a key place bids without per-bid human approval. You can't grant this yourself. Wripp staff grant it per organization after review. |
A request whose key lacks the scope returns 403 with code INSUFFICIENT_SCOPE.
Rate limits
Each key has its own limit in requests per minute. The limit follows the key across IPs and servers. The default is 60. You can set a higher limit when you create the key, up to your plan's ceiling:
| Plan | Max requests/min per key |
|---|---|
| FREE | 60 |
| STARTER | 120 |
| PRO | 300 |
| ELITE | 600 |
| ENTERPRISE | 1200 |
Responses include X-RateLimit-Limit and X-RateLimit-Remaining. When the limit is exceeded you get 429 with a Retry-After header (seconds). Plan limits for loads and bids still apply on top of the rate limit.
Idempotency
Every POST under /v1 requires an Idempotency-Key header of 1–255 characters, such as a UUID. Without one the request fails with 400 IDEMPOTENCY_KEY_REQUIRED.
- A successful response (status below 400) is stored for 24 hours. Sending the same key again returns the original status and body, with the header
Idempotent-Replayed: true, and nothing is written a second time. - Reusing a key for a different request (a different path or body) returns
422 IDEMPOTENCY_KEY_REUSED. - Failed responses are not stored, so you can retry a failed request with the same key.
- Stored responses belong to the secret that sent them. After a rotation, a retry sent with the new secret counts as a new request.
- Two identical requests sent at the same moment are not deduplicated. Retry sequentially.
Response envelope
Success responses (for /v1 and the public feed):
{ "data": { "...": "..." }, "meta": { "requestId": "6f1c…", "count": 20 } }count appears when data is a list. Some routes add more fields to meta, such as disclaimer or cacheSeconds.
Errors, on every route:
{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "…", "statusCode": 403, "requestId": "6f1c…", "timestamp": "…", "path": "/api/v1/loads", "details": {} } }Always quote requestId when you contact support.
Endpoints
POST /api/v1/loads (loads:write)
Creates a load with the same body and rules as the web post form: good standing, plan active-load quota, accuracyCertified: true to go live, and pickup at least 2 hours out. A load with both a pickup and a dropoff is POSTED immediately; otherwise it is saved as a DRAFT.
curl -X POST "$WRIPP/v1/loads" \
-H "x-api-key: $WRIPP_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" \
-d '{
"title": "Steel coils Dallas → Atlanta", "commodity": "Steel Coils", "equipmentType": "FLATBED",
"weightValue": 42000, "weightUnit": "LBS", "accuracyCertified": true,
"pickupWindowStart": "2026-09-20T15:00:00Z",
"stops": [
{ "stopType": "PICKUP", "city": "Dallas", "state": "TX", "postalCode": "75201" },
{ "stopType": "DROPOFF", "city": "Atlanta", "state": "GA", "postalCode": "30303" }
]
}'POST /api/v1/loads/:loadId/bids (bids:write)
Places a bid under the same rules as the web app: verification gate, good standing, blocks, bid quota, bidding close time, a GPS attestation when the load requires GPS, and one active bid per load. The body is the web bid body plus a required agent attestation:
curl -X POST "$WRIPP/v1/loads/$LOAD_ID/bids" \
-H "x-api-key: $WRIPP_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" \
-d '{ "bidAmount": 2350, "transitEtaText": "Next-day delivery",
"attestation": { "agent": "acme-dispatch/2.1", "humanApproved": true } }'attestation.agent: the agent or tool placing the bid (1–120 characters).attestation.humanApproved:trueonly if a person reviewed and approved this bid. If it'sfalseand the key doesn't holdbids:autonomous, the request is refused with403 HUMAN_APPROVAL_REQUIRED.
The attestation is stored on the bid together with the key id, key prefix and a timestamp.
GET /api/v1/loads/search (loads:read or market:read)
Returns open marketplace loads your org can bid on. Your own loads and shippers blocked with your org are hidden, and only public loads are listed. Filters (all optional): equipmentType, originState, destinationState, loadMode, maxWeightLbs, minRate, pickupFrom (YYYY-MM-DD), limit (≤100), cursor. For proximity search, pass originLat, originLng, radiusMiles (default 250) and sort=closest|newest, and page with offset.
curl "$WRIPP/v1/loads/search?originState=TX&equipmentType=REEFER&limit=20" -H "x-api-key: $WRIPP_API_KEY"GET /api/v1/loads/:id (loads:read)
Returns a load your org posted, or an open public load your org can discover. Anything else returns 404.
GET /api/v1/market-range (market:read)
Returns the historical range of booked amounts on a lane: count, low (p25), median, high (p75), perMile, and confidence (NONE below 3 samples, then LOW/MEDIUM/HIGH). It is a range, never a price recommendation. meta.disclaimer carries the neutrality statement; show it wherever you display the range.
curl "$WRIPP/v1/market-range?originState=TX&destinationState=GA&equipmentType=FLATBED" -H "x-api-key: $WRIPP_API_KEY"GET /api/v1/market/index (market:read, ELITE+ plan)
The anonymized weekly lane-rate index: per state pair and equipment family, the median booked rate per mile over the 4 weeks ending each week. A lane appears only when that window has at least 10 completed bookings from 3 or more shippers and 3 or more carriers, with no single organization supplying more than half, and organizations that turned off benchmark data sharing are excluded. Filters (all optional): originState, destinationState, equipment (a type such as DRY_VAN, matched to its family DRY_VAN+REEFER), weeks (1–104, default 12). Returns { weeks, truncated, lanes: [{ originState, destinationState, equipmentFamily, series: [{ weekStart, medianRatePerMile, sampleSize, changePct }] }] }. Below ELITE the request fails with 403 QUOTA_EXCEEDED (details.upgradeTier: "ELITE"). It is historical information, not pricing guidance; meta.disclaimer carries the statement.
curl "$WRIPP/v1/market/index?originState=TX&equipment=DRY_VAN&weeks=26" -H "x-api-key: $WRIPP_API_KEY"GET /api/v1/bids (bids:read)
Without parameters, returns your org's bids as a carrier. With ?loadId=, returns the bids on that load under the web visibility rules: the shipper sees all of them; a carrier sees the anonymized top 3 plus its own.
Existing read routes (unversioned, unchanged)
GET /api/public/loads (loads:read), GET /api/public/bookings (bookings:read), GET /api/public/invoices (ELITE+).
GET /api/public/feed/loads (no key)
An anonymized discovery feed of up to 100 of the newest open public loads. It is cached for 5 minutes and limited to 10 requests per minute per IP. Each item contains only:
{ "id": "…", "lane": { "originState": "TX", "destinationState": "GA" }, "equipmentType": "FLATBED", "loadMode": "FTL",
"hazmat": false, "weightBand": "40K_PLUS_LBS", "pickupDate": "2026-09-20", "postedAt": "2026-09-12T10:00:00.000Z" }The feed never includes the shipper's identity, cities or addresses, commodity, notes, or rates. To bid, use id with an API key.
Who is recorded as acting
An API key belongs to an organization, and its scopes decide what it can do. Loads and bids still record a responsible user:
1. The user who created the key, if that user is still an active member of the org. 2. Otherwise, the org's longest-standing active owner. This covers keys created before scopes existed and keys whose creator has left. 3. If neither exists, writes are refused with 403 API_KEY_NO_ACCOUNTABLE_USER. Create a new key.
Every API write also adds an audit log entry (API_V1_LOAD_CREATED, API_V1_BID_PLACED) with the API key id, key prefix and Idempotency-Key.
