PC

PostCargo REST API

Documentatie completa · v1.0 · Production-ready

API-ul PostCargo permite oricarui magazin online sau ERP sa integreze direct serviciile noastre de curierat: calcul preturi pe 12 curieri, generare AWB automata, tracking live, webhooks pentru status updates si retururi. Foloseste-l prin pluginuri oficiale (WooCommerce, Shopify) sau direct prin REST.

12
curieri integrati
200ms
latenta medie
99.9%
uptime
Base URL
https://api.postcargo.ro

Cele mai populare endpoint-uri

Autentificare

API-ul foloseste doua tipuri de autentificare:

1

X-API-Key

Pentru integrari (WooCommerce, ERP, scripts). O cheie pe integrare, revocabila oricand.

X-API-Key: pcg_live_xxxxxxxxxxxxxxxx
2

Bearer JWT

Pentru actiuni administrative (gestionare chei, setari tenant). Obtinut prin login.

Authorization: Bearer eyJhbGc...
Ambele in acelasi request — pentru operatiuni administrative care folosesc si X-API-Key, trimite ambele headere simultan. JWT autentifica utilizatorul, X-API-Key identifica integrarea.

Format raspuns

Toate raspunsurile au structura:

{
  "success": true,
  "data": { ... },
  "meta": { "page": 1, "total": 42 }
}

Erori:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Missing required field: weight"
  }
}

Coduri eroare

HTTP Code Cauza
400VALIDATION_ERRORCamp lipsa sau invalid
401UNAUTHORIZEDCheie API invalida sau expirata
403FORBIDDENPermisiune insuficienta
404NOT_FOUNDResursa inexistenta
429RATE_LIMITEDPrea multe cereri (vezi headerul Retry-After)
500INTERNAL_ERROREroare server (logata pe partea noastra)
503COURIER_UNAVAILABLEAPI curier offline temporar

Rate limiting

Limita implicita: 100 cereri / minut per cheie API. Headerele de raspuns:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1714650000
Retry-After: 12

Limita poate fi crescuta pentru clientii cu volum mare. Contact: suport@postcargo.ro

Permisiuni

Cheile API pot avea permisiuni granulare. Implicit, cheile noi au ["*"] (acces complet).

pricing:readCalculeaza preturi
shipments:createCreeaza expediere
shipments:readCiteste expedieri
shipments:cancelAnuleaza expediere
awb:createGenereaza AWB
awb:readDescarca AWB PDF
tracking:readTracking
webhooks:manageWebhooks
api_keys:manageGestioneaza chei (necesita JWT)
tenant:manageSetari cont (necesita JWT)

Auth & Conturi

POST /v1/auth/register Public

Creeaza un cont nou + tenant + prima cheie API.

Contul necesita aprobare manuala

Fiecare cont API este verificat de echipa PostCargo. Pana la aprobare, contul are status: "pending", nu primesti JWT, iar cheia API returnata este respinsa cu 403. Primesti email cand contul este activat, apoi te autentifici cu POST /v1/auth/login.

Body

{
  "tenant_name": "Magazinul Meu SRL",
  "email": "owner@magazin.ro",
  "password": "parola_min_8",
  "name": "Ion Popescu",
  "phone": "0722123456"
}

phone este optional, dar grabeste verificarea contului.

Response 201

{
  "success": true,
  "data": {
    "user": { "id": 1, "email": "owner@magazin.ro", "role": "owner" },
    "tenant": { "id": 1, "name": "Magazinul Meu SRL", "slug": "magazinul-meu-srl", "status": "pending" },
    "api_key": "pcg_live_a1b2c3d4e5...",
    "status": "pending_approval",
    "message": "Contul a fost creat si asteapta aprobarea echipei PostCargo."
  }
}

Salveaza api_key acum: nu mai este afisata a doua oara. Devine functionala dupa aprobare.

POST /v1/auth/login Public

Login cu email + parola. Returneaza JWT + refresh token.

curl -X POST https://api.postcargo.ro/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"owner@magazin.ro","password":"parola"}'

Erori specifice

CodHTTPCe inseamna
ACCOUNT_PENDING403Contul nu a fost aprobat inca. Aparea email la activare.
ACCOUNT_SUSPENDED403Contul a fost suspendat. Contacteaza PostCargo.
AUTHENTICATION_ERROR401Email sau parola greşite.
POST /v1/auth/refresh

Reinnoieste JWT folosind refresh token.

{ "refresh_token": "rfk_..." }
GET /v1/auth/api-keys JWT + API Key

Listeaza toate cheile API ale tenant-ului — cu request_count + last_used_at + last_used_ip.

{
  "success": true,
  "data": [
    {
      "id": 1,
      "key_prefix": "pcg_live_a1b2",
      "label": "WooCommerce - Magazin principal",
      "permissions": ["*"],
      "is_active": true,
      "last_used_at": "2026-05-03T20:30:12+03:00",
      "last_used_ip": "82.77.123.45",
      "request_count": 1432,
      "expires_at": null,
      "created_at": "2026-05-01T18:00:00+03:00"
    }
  ]
}
POST /v1/auth/api-keys

Genereaza o cheie noua. Cheia complet apare o singura data in raspuns; salveaza-o.

// Body
{
  "label": "WooCommerce - magazin nou",
  "permissions": ["pricing:read", "shipments:create", "awb:create"],
  "expires_in_days": 365
}

// Response
{
  "success": true,
  "data": {
    "id": 5,
    "label": "WooCommerce - magazin nou",
    "api_key": "pcg_live_xxxxxxxxxxxxxxxxxxxx",
    "key_prefix": "pcg_live_xxxx"
  }
}
GET /v1/auth/api-keys/{id}/logs

Loguri detaliate pe o cheie. Filtre disponibile: ?page=1&per_page=50&status=200&platform=woocommerce&from=2026-05-01&to=2026-05-31

{
  "success": true,
  "data": {
    "key": { "id": 1, "label": "...", "request_count": 1432 },
    "stats_24h": { "total": 245, "success": 240, "errors": 5, "avg_ms": 187 },
    "logs": [
      {
        "id": 9876, "method": "POST", "endpoint": "/v1/pricing/quote",
        "status_code": 200, "duration_ms": 215, "ip": "82.77.x",
        "source_platform": "woocommerce", "created_at": "..."
      }
    ]
  },
  "meta": { "page": 1, "per_page": 50, "total": 1432, "total_pages": 29 }
}
GET /v1/auth/api-keys/usage

Stats agregate (toate cheile, ultimele 7-90 zile). ?days=7

{
  "data": {
    "totals": { "total": 12450, "success": 12200, "errors": 250, "avg_ms": 195 },
    "by_day": [{ "day": "2026-05-01", "total": 1820, "success": 1810, "errors": 10 }],
    "by_platform": [{ "platform": "woocommerce", "total": 11230 }],
    "top_endpoints": [{ "endpoint": "/v1/pricing/quote", "total": 6420 }]
  }
}
DELETE /v1/auth/api-keys/{id}

Revoca cheia. Integrarea care o foloseste va inceta sa functioneze imediat.

POST /v1/auth/forgot-password Public

Trimite pe email linkul de resetare a parolei. Raspunsul este acelasi si daca emailul nu exista, ca sa nu se poata afla ce conturi sunt inregistrate.

{ "email": "owner@magazin.ro" }
GET /v1/auth/reset-password/validate Public

Verifica daca un token de resetare este inca valid, inainte de a afisa formularul. Parametru query: ?token=...

POST /v1/auth/reset-password Public

Schimba parola folosind tokenul primit pe email. Tokenul se consuma o singura data si invalideaza sesiunile existente.

{ "token": "...", "password": "parola_noua_min_8" }

Public (fara auth)

GET /health Public
{ "success": true, "data": { "status": "ok", "version": "1.0.0", "php": "8.3.30" } }
GET /v1/couriers Public

Lista publica a celor 12 curieri integrati (logo, descriere, pret pornire, features).

Pricing

POST /v1/pricing/quote

Calculeaza pretul pentru toti curierii activi simultan. Recomandat pentru checkout in WooCommerce/Shopify.

Body

{
  "from": { "city": "Bucuresti", "county": "Bucuresti", "postcode": "010101" },
  "to":   { "city": "Cluj-Napoca", "county": "Cluj", "postcode": "400001" },
  "package": {
    "type": "colet",
    "weight": 2.5,
    "length": 30, "width": 20, "height": 15,
    "value_declared": 250,
    "cod_amount": 0
  },
  "options": {
    "saturday_delivery": false,
    "open_on_delivery": false,
    "insurance": true
  }
}

Trimite length, width si height: curierii factureaza greutatea volumetrica (L x l x h / 5000) cand e mai mare decat cea reala, iar fara dimensiuni un colet mare si usor este cotat sub tariful real.

Response

{
  "success": true,
  "data": [
    {
      "courier_code": "gls",
      "courier_name": "GLS Romania",
      "price": 19.57,
      "price_without_vat": 19.57,
      "price_with_vat": 23.68,
      "vat_rate": 21,
      "list_price_without_vat": 26.10,
      "list_price_with_vat": 31.58,
      "discount_percent": 25,
      "discount_amount": 7.90,
      "discount_name": "Reducere Cont Nou",
      "discount_source": "site",
      "estimated_days": "1-3",
      "source": "live",
      "currency": "RON"
    }
  ]
}

Preţ, TVA si reducere

CampCe contine
list_price_without_vatTariful de lista, inainte de reducere (fara TVA).
price / price_without_vatCat se plateste, fara TVA, dupa reducere. Acesta e costul de transport pe care il pui in checkout.
price_with_vatAcelasi preţ, cu TVA inclus.
discount_sourcesite = reducerea activa pe postcargo.ro, preluata automat. tenant = procent setat pentru contul tau. none = fara reducere.
sourcelive = tarif real al curierului. estimated = tarif de rezerva din configuratie, folosit cand curierul nu raspunde; poate diferi de cel real.

Reducerea se aplica peste suma cu TVA, exact ca pe postcargo.ro, apoi se recalculeaza valoarea fara TVA. Implicit este cea activa pe site; poate fi schimbata per cont de echipa PostCargo.

POST /v1/pricing/calculate

Pret pentru un singur curier specific. Body identic cu /quote, plus "courier": "fan-courier".

POST /v1/pricing/calculate-all

Preturile de la toti curierii activi pe contul tau, intr-un singur apel. Body identic cu /quote. Util pentru a afisa clientului toate opţiunile la checkout.

GET /v1/pricing/couriers

Curierii pentru care contul tau poate cere preturi, cu marja configurata. Diferenta faţă de /v1/couriers: acela e public si general, acesta reflecta configuraţia contului.

Localitati

Nomenclatorul de localitati folosit de postcargo.ro. Foloseste-l pentru autocomplete in checkout: tariful depinde de judet, iar un oras scris greşit duce la alt preţ sau la o expediere respinsa.

GET /v1/geo/cities

Localitatile care incep cu textul cautat. Parametri: q (minim 1 caracter) si limit (implicit 15, maxim 50).

curl "https://api.postcargo.ro/v1/geo/cities?q=clu&limit=5" \
  -H "X-API-Key: pcg_live_..."

Response

{
  "success": true,
  "data": [
    {
      "value": "Cluj-Napoca (Cluj)",
      "label": "Cluj-Napoca",
      "id": 12345,
      "county": "Cluj",
      "county_id": 14,
      "postal_code": "400001"
    }
  ]
}

Trimite label ca oras si county ca judet cand ceri preţul sau creezi expedierea.

Bucuresti se expediaza pe sector

Pentru q=bucuresti primesti cele 6 sectoare, fiecare cu sector: 1..6, nu orasul generic. Curierii tarifeaza diferit pe sector, iar o adresa fara sector poate fi respinsa la ridicare.

GET /v1/geo/counties

Toate judetele, cu numele exact asteptat de API la calculul de preţ si la creare expediere.

Expedieri

POST /v1/shipments

Creeaza expediere fara generare AWB (status: draft). AWB se genereaza separat (vezi /v1/awb).

{
  "courier_code": "fan-courier",
  "external_id": "wc_order_1234",
  "sender": {
    "name": "Magazinul Meu", "phone": "0712345678",
    "city": "Bucuresti", "county": "Bucuresti",
    "address": "Str Exemplu 10", "postcode": "010101"
  },
  "recipient": {
    "name": "Maria Popescu", "phone": "0723456789",
    "city": "Cluj-Napoca", "county": "Cluj",
    "address": "Str Aurel Vlaicu 25", "postcode": "400001"
  },
  "package": {
    "type": "colet", "weight": 2.5,
    "length": 30, "width": 20, "height": 15,
    "value_declared": 250,
    "cod_amount": 0,
    "content": "Carti, articole personale"
  }
}
GET /v1/shipments

Filtre: ?status=delivered&courier=fan-courier&page=1&per_page=50&from=2026-05-01

GET /v1/shipments/{id}

Detaliile complete (sender, recipient, package, awb, tracking history).

POST /v1/shipments/{id}/cancel

Anuleaza AWB-ul la curier si scoate expedierea din facturare. Body optional: {"reason": "Cumparator a anulat"}

Forma echivalenta: DELETE /v1/shipments/{id}. O expediere deja livrata sau anulata returneaza 400 VALIDATION_ERROR.

{
  "success": true,
  "data": {
    "message": "Shipment cancelled",
    "awb_cancelled_at_courier": true
  }
}

Anularea se face intai la curier. Daca AWB-ul exista, il anulam la curier si abia apoi marcam expedierea cancelled. Cand curierul refuza (coletul e deja preluat, fereastra de anulare a expirat), primesti 502 COURIER_CANCEL_FAILED cu motivul lui, iar expedierea rămâne activa si facturabila.

Facturare. Expedierea anulata iese automat din facturarea consolidata, deci nu apare pe factura urmatoare. Daca era deja pe o factura emisa, raspunsul include billing_note: suma se corecteaza doar prin storno, nu prin anulare.

DELETE /v1/shipments/{id}

Acelasi efect ca POST /v1/shipments/{id}/cancel: anuleaza AWB-ul la curier, trece expedierea in cancelled si o scoate din facturare. Nu sterge inregistrarea, ca sa ramana in istoric.

GET /v1/shipments/{id}/tracking

Starea curenta a expedierii. Daca expedierea nu are inca AWB, raspunsul este 400 VALIDATION_ERROR — genereaza-l intai.

{
  "success": true,
  "data": {
    "awb_number": "1234567890",
    "status": "in_transit",
    "courier_code": "fan-courier",
    "recipient": { "name": "Ion Popescu", "city": "Cluj-Napoca", "county": "Cluj" },
    "created_at": "2026-05-03 09:15:00",
    "updated_at": "2026-05-03 18:30:00"
  }
}

Valori posibile pentru status: draft, pending, awb_generated, picked_up, in_transit, out_for_delivery, delivered, returned, cancelled.

GET /v1/tracking/{awb}

Aceleasi date, dar cautate dupa numarul AWB in loc de id-ul expedierii. Util cand stochezi doar AWB-ul. Raspunsul este identic cu cel de mai sus.

AWB (Air Waybill)

POST /v1/shipments/{shipment_id}/awb

Genereaza AWB la curierul ales. Returneaza numarul AWB + calea PDF-ului.

Forma echivalenta, acceptata si ea: POST /v1/awb/{shipment_id}. Daca expedierea are deja AWB, raspunsul este 400 VALIDATION_ERROR.

{
  "success": true,
  "data": {
    "awb_number": "1234567890",
    "price": 24.20,
    "cost_price": 18.00,
    "status": "awb_generated",
    "pdf_url": "/v1/shipments/8/awb/pdf"
  }
}
GET /v1/shipments/{shipment_id}/awb/pdf

Returneaza PDF binar al AWB-ului. Headers: Content-Type: application/pdf.

Forma echivalenta: GET /v1/awb/{id}/pdf, unde {id} poate fi id-ul expedierii sau numarul AWB. Ruta cere acelasi header de autentificare ca restul API-ului, deci linkul nu poate fi deschis direct in browser.

Tenant (cont)

GET /v1/tenant/settings

Datele contului: nume, slug, domeniu, plan, stare si setarile proprii.

PUT /v1/tenant/settings

Actualizeaza datele contului. Planul si starea contului se schimba doar de PostCargo.

{ "name": "Magazinul Meu SRL", "domain": "magazin.ro" }
GET /v1/tenant/billing

Datele fiscale ale contului si ce mai lipseste pentru a putea emite factura. status.is_complete spune daca poti expedia.

{
  "success": true,
  "data": {
    "billing": {
      "billing_company": "MAGAZINUL MEU SRL",
      "billing_cui": "14399840",
      "billing_reg_com": "J40/1234/2020",
      "billing_address": "Str. Exemplu 12",
      "billing_city": "Bucuresti",
      "billing_county": "Bucuresti",
      "billing_postal": "010011",
      "billing_iban": "RO49AAAA1B31007593840000",
      "billing_bank": "Banca Transilvania",
      "billing_email": "facturi@magazin.ro"
    },
    "status": { "is_complete": true, "missing_fields": [], "missing_labels": [] }
  }
}
PUT /v1/tenant/billing

Salveaza datele fiscale. Poti trimite doar o parte din campuri; CUI-ul si IBAN-ul sunt validate (cifra de control), iar prefixul RO din CUI si spatiile din IBAN se normalizeaza automat.

Obligatoriu inainte de prima expediere. Expedierile prin API se factureaza periodic, nu se plateasc pe comanda, deci fara denumire, CUI, adresa, oras, judet, IBAN si banca nu putem emite factura. POST /v1/shipments raspunde cu 422 BILLING_DATA_REQUIRED pana cand sunt completate.
{
  "billing_company": "MAGAZINUL MEU SRL",
  "billing_cui": "RO14399840",
  "billing_address": "Str. Exemplu 12",
  "billing_city": "Bucuresti",
  "billing_county": "Bucuresti",
  "billing_iban": "RO49 AAAA 1B31 0075 9384 0000",
  "billing_bank": "Banca Transilvania"
}
GET /v1/tenant/couriers

Lista celor 12 curieri PostCargo cu detalii (logo, descriere, features, pret pornire) si flag is_enabled pentru tenant.

Important: nu trebuie sa-ti pui credentialele curierilor. Folosesti contractele PostCargo automat — preturi negociate, AWB direct prin platforma noastra.
PUT /v1/tenant/couriers/{code}

Activeaza/dezactiveaza un curier pentru contul tau. Body: {"is_active": true}

DELETE /v1/tenant/couriers/{code}

Scoate complet curierul din configuraţia contului. Expedierile deja create cu el rămân neatinse.

GET /v1/tenant/stats

Stats agregate: total expedieri, by status, by courier, ultimele 5.

GET /v1/tenant/seo

Setarile pentru pagina publica de tracking a coletelor tale (titlu, descriere, widget).

PUT /v1/tenant/seo

Actualizeaza setarile SEO ale paginii de tracking.

Webhooks

GET /v1/webhooks

Lista webhook-urilor configurate.

POST /v1/webhooks

Configureaza un webhook care primeste evenimente (shipment.created, shipment.delivered, awb.generated, etc).

{
  "url": "https://magazin.ro/postcargo-webhook",
  "events": ["shipment.delivered", "shipment.cancelled"],
  "secret": "secret_pentru_HMAC"
}
DELETE /v1/webhooks/{id}

Sterge webhook-ul. Nu mai primeşti evenimente pe acel URL.

Integrari (publice)

GET /v1/integrations Public

Lista pluginurilor disponibile (WooCommerce activ; Shopify, PrestaShop in lucru).

GET /v1/integrations/woocommerce/info Public

Versiune curenta plugin, cerinte WP/WooCommerce, marime, data ultimului build.

GET /v1/integrations/woocommerce/download Public

Descarca postcargo-shipping.zip direct. Auto-build cand sursa devine mai noua decat .zip-ul cached.

Descarca acum (.zip)