PortChief API — Developer Documentation
PortChief API — Developer Documentation
Manage container listings server-to-server over a small REST API: authenticate once, then create, read, search, update and withdraw listings.
Base URL https://portchief.com/wp-json/portchief/v1
ℹ️ The examples below use the full URL (with https:// and the host) — copy-paste them as-is, nothing to concatenate.
🚀 Quick start
- Exchange your PortChief credentials for a token at
POST /auth/token. - Send that token as
Authorization: Bearer <token>on every call. - Create listings with
POST /listings. - Read, search, update or withdraw them with
GET/PATCH/DELETE /listings. Done.
1 · Authentication
Exchange a WordPress username + password for a signed JWT. The token is valid 30 days.
Request
POST https://portchief.com/wp-json/portchief/v1/auth/token
Content-Type: application/json
{
"username": "your-account",
"password": "your-password"
}
Response · 200 OK
{
"token": "eyJhbGciOiJIUzI1Ni␣...",
"token_type": "Bearer",
"expires_in": 2592000,
"user": { "id": 4, "email": "you@example.com", "name": "Your Name" }
}
Bad credentials return 401 { "code": "pc_auth_failed" }.
Using the token
Authorization: Bearer eyJhbGciOiJIUzI1Ni␣...
2 · Create a listing
Creates one published container listing. One listing = one container (atomic). Your organization is taken from your account — never from the payload.
Required fields
| Field | Type | Rule |
|---|---|---|
direction * |
string | offer or need |
container_type * |
string | 40HC · 40DC · 20DC · Reefer · Tank · Open Top · Flat Rack · Hazmat |
city * |
string | City/port slug (e.g. marseille-fos) |
address or lat+lng * |
string / number | At least one. An address alone is geocoded automatically (422 if unresolved). |
Optional fields
| Field | Type | Rule / default |
|---|---|---|
pickup_window |
object | { "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" } — strict ISO 8601, start ≤ end. start defaults to today; end open if omitted. |
price |
number | Offers only. Default 0 (free). Currency is the site base currency (EUR). |
description |
string | Free text, ≤ 5000 chars |
image_base64 |
string | jpeg / png / webp, ≤ 5 MB (data-URI prefix accepted) |
title |
string | Auto-generated if omitted |
Example request
POST https://portchief.com/wp-json/portchief/v1/listings
Authorization: Bearer <token>
Content-Type: application/json
{
"direction": "offer",
"container_type": "40HC",
"city": "marseille-fos",
"lat": 43.34, "lng": 4.99,
"description": "Reefer-ready 40HC, clean box.",
"pickup_window": { "start": "2026-06-20", "end": "2026-06-30" },
"price": 150
}
Response · 201 Created
{
"id": 659463,
"url": "https://portchief.com/listing/offre-40hc-marseille-fos/",
"status": "publish"
}
3 · Read a listing
Returns the full, enriched view of any published listing (so you can confirm what the server stored and reconcile your own system).
GET https://portchief.com/wp-json/portchief/v1/listings/659463
Authorization: Bearer <token>
Response · 200 OK
{
"id": 659463,
"url": "https://portchief.com/listing/offre-40hc-marseille-fos/",
"status": "publish",
"direction": "offer",
"title": "Offre — 40HC · Marseille-Fos",
"city": { "slug": "marseille-fos", "name": "Marseille-Fos" },
"container_type": { "slug": "40-hc", "label": "40HC" },
"coords": { "lat": 43.34, "lng": 4.99 },
"pickup_window": { "start": "2026-06-20", "end": "2026-06-30" },
"price": 150,
"contact": { "email": "you@example.com", "phone": null, "website": null },
"created_at": "2026-06-16T12:57:31+02:00",
"updated_at": "2026-06-17T09:49:00+02:00"
}
Unknown / withdrawn id returns 404 { "code": "pc_not_found" }.
4 · Search listings
List and filter listings. Parameters go in the URL query string (a GET has no body): ?key=value&key2=value2.
| Param | Type | Filter |
|---|---|---|
direction |
string | offer / need |
city |
string | city/port slug |
container_type |
string | 40HC, 20DC, Reefer… |
pickup_from / pickup_to |
date | ISO 8601 window on the pickup start |
lat + lng + radius_km |
number | Proximity search — listings within the radius, sorted by distance; each result then carries distance_km |
mine |
bool | mine=1 — only your own listings (the author comes from the token; withdrawn listings never appear) |
page / per_page |
int | Pagination (per_page default 20, max 100) |
GET https://portchief.com/wp-json/portchief/v1/listings?direction=need&container_type=reefer&lat=43.34&lng=4.99&radius_km=50
Authorization: Bearer <token>
Response · 200 OK
{
"results": [ { /* same shape as “Read a listing” */ "distance_km": 12.3 } ],
"page": 1, "per_page": 20, "total": 7, "total_pages": 1
}
distance_km (km, 1 decimal) is included only in proximity search (lat+lng+radius_km) — plain searches and Read a listing don’t have it.
5 · Update a listing
Update one of your own listings (others return 403). Send only the fields you want to change; the listing id stays stable.
PATCH https://portchief.com/wp-json/portchief/v1/listings/659463
Authorization: Bearer <token>
Content-Type: application/json
{
"pickup_window": { "start": "2026-07-01" }
}
Returns 200 with the updated listing (same shape as “Read”). Invalid fields → 400 with data.fields. Note: switching an offer to need drops its price.
6 · Withdraw a listing
Withdraw one of your own listings once the container is gone — it immediately disappears from the marketplace. Soft delete (recoverable), and idempotent: calling it again still returns 200.
DELETE https://portchief.com/wp-json/portchief/v1/listings/659463
Authorization: Bearer <token>
Response · 200 OK
{ "id": 659463, "status": "withdrawn" }
7 · Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | pc_validation |
Invalid payload. Offending fields are listed under data.fields. |
| 401 | pc_auth_failed / — |
Missing, invalid, or expired token. |
| 403 | pc_no_org / pc_forbidden |
Your account has no organization, or you tried to modify a listing that isn’t yours. |
| 404 | pc_not_found |
No such listing (unknown id, not a listing, or withdrawn). |
| 422 | pc_geocode_failed |
The address could not be located. Provide lat/lng or refine it. |
Validation error example · 400
{
"code": "pc_validation",
"message": "Invalid listing payload.",
"data": {
"status": 400,
"fields": {
"pickup_window.start": "Invalid date '20226'. Use strict ISO 8601 YYYY-MM-DD."
}
}
}
Notes
- Versioning — the API is namespaced
/v1. New optional fields may be added without breaking changes; any breaking change ships under a new version. - Quantity — fixed at 1 per listing. To post N containers, send N requests.
- Contact — buyers reach you through the contact details on your organization profile.
⬇ Download the Postman collection
Import it into Postman for all 6 endpoints, automatic token capture, and ready-to-run examples (happy paths + every error case).
