Skip to content

CourierHub API v2 - Επισκόπηση ​

Το CourierHub API v2 επιτρέπει στο σύστημά σας (ERP, e-shop, εσωτερικά εργαλεία) να δημιουργεί και να διαχειρίζεται παραγγελίες, να διαβάζει τις αποστολές, το tracking και τον λογαριασμό σας, να εκδίδει και να ακυρώνει vouchers, και να λαμβάνει ειδοποιήσεις σε πραγματικό χρόνο μέσω webhooks.

Προηγούμενη έκδοση

Το API v1 εξακολουθεί να λειτουργεί (έκδοση voucher, tracking). Το v2 έχει ξεχωριστό όριο κλήσεων και διαφορετική μορφή απαντήσεων.

Base URL ​

https://courierhub.gr/api/v2

Όλες οι απαντήσεις είναι JSON (ακόμα και τα σφάλματα, ανεξάρτητα από την κεφαλίδα Accept).

Authentication ​

Όλες οι κλήσεις απαιτούν την κεφαλίδα:

Authorization: Bearer chub_YOUR_API_TOKEN

Κάθε κατάστημα έχει δικό του API Token που ξεκινά με chub_. Το βρίσκετε και το ανανεώνετε στο περιβάλλον πελάτη (/app/settings): Ρυθμίσεις > Διασύνδεση & Καταστήματα > Webhook Settings > (κατάστημα) > API Token. Με το κουμπί Ανανέωση Token δημιουργείται νέο token και το παλιό σταματά αμέσως να λειτουργεί.

Προσοχή

  • Το API Token είναι μυστικό. Μην το βάζετε σε κώδικα που φτάνει στον browser ή σε URLs.
  • Το webhook token του καταστήματος (το UUID που υπάρχει στα webhook URLs του e-shop) δεν γίνεται δεκτό από το v2. Μόνο το API Token chub_....
  • Το token ταυτοποιεί το κατάστημα, αλλά τα δεδομένα που βλέπετε είναι όλα τα δεδομένα του λογαριασμού σας (όλων των καταστημάτων του πελάτη), όχι μόνο του καταστήματος του token.

Παράδειγμα σφάλματος (χωρίς token ή με λάθος token)

json
{
    "message": "Missing API token",
    "code": "unauthenticated"
}

Όριο κλήσεων (rate limit) ​

Το όριο είναι 60 κλήσεις το λεπτό ανά κατάστημα, κοινό για όλα τα endpoints του /api/v2. Έχει ξεχωριστό budget από το v1: οι κλήσεις v1 δεν μετρούν στο v2 και αντίστροφα.

Κάθε απάντηση (εκτός από τα σφάλματα ταυτοποίησης 401 και τα 404/405 για άγνωστα URLs) περιλαμβάνει:

HeaderΠεριγραφή
X-RateLimit-LimitΤο μέγιστο πλήθος κλήσεων ανά λεπτό.
X-RateLimit-RemainingΠόσες κλήσεις απομένουν στο τρέχον λεπτό.
Retry-AfterΜόνο στο 429: μετά από πόσα δευτερόλεπτα να ξαναδοκιμάσετε.

Παράδειγμα υπέρβασης ορίου (429 Too Many Requests)

Headers: X-RateLimit-Limit: 3, X-RateLimit-Remaining: 0, Retry-After: 60

json
{
    "message": "Too many requests. Retry after 60 seconds.",
    "code": "rate_limited"
}

(Το παράδειγμα προέρχεται από περιβάλλον δοκιμής με όριο 3 κλήσεις. Στην παραγωγή το X-RateLimit-Limit είναι 60.)

Σελιδοποίηση (cursor pagination) ​

Τα endpoints που επιστρέφουν λίστες (GET /orders, GET /shipments, GET /balance/transactions, GET /webhook-endpoints, GET /webhook-endpoints/{id}/deliveries) χρησιμοποιούν cursor pagination.

ΠαράμετροςΤύποςΠεριγραφή
per_pageIntegerΠόσα αποτελέσματα ανά σελίδα, από 1 έως 100. Προεπιλογή 50.
cursorStringΑδιαφανές (opaque) token που δίνεται από την προηγούμενη σελίδα. Μην το τροποποιείτε.

Η απάντηση έχει πάντα τη μορφή {data, links, meta}:

ΠεδίοΤύποςΠεριγραφή
dataArrayΤα αποτελέσματα της σελίδας.
links.nextString ή nullΠλήρες URL για την επόμενη σελίδα (κρατάει τα φίλτρα και το per_page). null στην τελευταία σελίδα.
links.prevString ή nullΠλήρες URL για την προηγούμενη σελίδα.
meta.per_pageIntegerΤο μέγεθος σελίδας που εφαρμόστηκε.
meta.next_cursorString ή nullΤο cursor της επόμενης σελίδας.
meta.prev_cursorString ή nullΤο cursor της προηγούμενης σελίδας.

Για να διαβάσετε όλα τα αποτελέσματα ακολουθείτε το links.next μέχρι να γίνει null. Δεν υπάρχει συνολικό πλήθος (total).

Παράδειγμα: φόρτωση όλου του ιστορικού ​

Για λογαριασμό με περίπου 16.000 αποστολές και per_page=100 χρειάζονται περίπου 160 κλήσεις. Με όριο 60 κλήσεων το λεπτό, η πλήρης φόρτωση παίρνει περίπου 3 λεπτά. Το script παρακάτω ακολουθεί το links.next και, αν πάρει 429, περιμένει όσα δευτερόλεπτα λέει το Retry-After:

php
<?php

$token = getenv('COURIERHUB_API_TOKEN');
$url = 'https://courierhub.gr/api/v2/shipments?per_page=100&sort=created_at';
$count = 0;

while ($url !== null) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => true,
        CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", 'Accept: application/json'],
    ]);
    $response = curl_exec($ch);

    if ($response === false) {
        throw new RuntimeException('Connection error: '.curl_error($ch));
    }

    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $headers = substr($response, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
    $body = substr($response, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
    curl_close($ch);

    if ($status === 429) {
        preg_match('/^retry-after:\s*(\d+)/mi', $headers, $m);
        sleep((int) ($m[1] ?? 60));

        continue; // ξανά το ίδιο URL
    }

    if ($status !== 200) {
        throw new RuntimeException("HTTP {$status}: {$body}");
    }

    $page = json_decode($body, true);

    foreach ($page['data'] as $shipment) {
        $count++;
        // ... αποθήκευση / επεξεργασία της αποστολής
    }

    $url = $page['links']['next']; // null στην τελευταία σελίδα
}

echo "Φορτώθηκαν {$count} αποστολές\n";

Συμβουλή

Η φόρτωση ιστορικού είναι εφάπαξ. Για τρέχουσες αλλαγές χρησιμοποιήστε τα webhooks αντί για συνεχές polling: δεν καταναλώνουν το όριο κλήσεων.

Μορφή σφαλμάτων ​

Κάθε σφάλμα έχει τη μορφή:

json
{
    "message": "Κείμενο για άνθρωπο (στα Αγγλικά)",
    "code": "σταθερός_κωδικός",
    "errors": { "πεδίο": ["μήνυμα"] }
}

Το errors υπάρχει μόνο στα σφάλματα επικύρωσης (validation_failed, courier_validation_failed). Τα σφάλματα 409 (σύγκρουση με την τρέχουσα κατάσταση ενός πόρου) έχουν μόνο message και code. Το message προορίζεται για ανθρώπους και μπορεί να αλλάξει. Για αυτοματισμούς βασιστείτε στο HTTP status και στο code.

codeHTTPΠότε
unauthenticated401Λείπει το Bearer token ή δεν είναι έγκυρο (π.χ. webhook token αντί για API Token).
forbidden403Δεν επιτρέπεται η ενέργεια.
not_found404Ο πόρος δεν υπάρχει, δεν ανήκει στον λογαριασμό σας, ή το URL δεν υπάρχει.
method_not_allowed405Λάθος HTTP method για το URL. Η κεφαλίδα Allow λέει ποια επιτρέπονται.
order_number_taken409Το order_number το χρησιμοποιεί ήδη άλλη παραγγελία του καταστήματος (POST / PATCH /orders).
order_has_shipment409Η παραγγελία έχει voucher και δεν αλλάζει ούτε διαγράφεται (PATCH / DELETE /orders/{id}).
validation_failed422Μη έγκυρες παράμετροι. Το errors έχει τα προβλήματα ανά πεδίο.
rate_limited429Υπέρβαση του ορίου κλήσεων. Δείτε το Retry-After.
server_error5xxΣφάλμα στην πλευρά του CourierHub. Δοκιμάστε ξανά αργότερα.
client_errorάλλα 4xxΟποιοδήποτε άλλο σφάλμα πελάτη (π.χ. 400, 413).

Επιπλέον κωδικοί των κλήσεων έκδοσης, ακύρωσης και ετικέτας αποστολής:

codeHTTPΠότε
shipment_limit_reached422Έχετε φτάσει το όριο αποστολών της τρέχουσας περιόδου.
insufficient_balance422Το διαθέσιμο υπόλοιπο δεν καλύπτει το κόστος του voucher.
courier_not_connected422Δεν έχετε συνδέσει λογαριασμό με την courier.
courier_not_supported422Η courier δεν υποστηρίζει vouchers μέσω CourierHub.
courier_validation_failed422Η courier απέρριψε τα στοιχεία της παραγγελίας. Το errors έχει τα προβλήματα ανά πεδίο.
courier_cancellation_unsupported422Η courier δεν υποστηρίζει ακύρωση μέσω API (όλες εκτός από ACS, BoxNow και ELTA).
shipment_in_progress409Άλλη κλήση εκδίδει αυτή τη στιγμή voucher για την ίδια παραγγελία και courier. Δείτε το Retry-After.
courier_error502Η courier απέτυχε. Το message περιγράφει το σφάλμα, άλλοτε με το μήνυμα της ίδιας της courier και άλλοτε με δική μας περιγραφή (π.χ. No voucher number returned from ACS API). Είναι για ανθρώπους: για τον χειρισμό στηριχτείτε στο code.
label_unavailable502Η courier δεν επέστρεψε αρχείο ετικέτας.
voucher_charge_failed500Το voucher εκδόθηκε, αλλά απέτυχε η χρέωση. Η απάντηση έχει επιπλέον το πεδίο voucher_number. Μια επανάληψη της ίδιας κλήσης επιστρέφει το voucher με 200.

Παράδειγμα σφαλμάτων

404 Not Found

json
{
    "message": "Resource not found.",
    "code": "not_found"
}

405 Method Not Allowed (με κεφαλίδα Allow: GET, HEAD)

json
{
    "message": "Method not allowed.",
    "code": "method_not_allowed"
}

422 Unprocessable Content

json
{
    "message": "The given data was invalid.",
    "code": "validation_failed",
    "errors": {
        "per_page": [
            "The per page field must not be greater than 100."
        ],
        "status": [
            "The selected status is invalid."
        ],
        "created_from": [
            "The created from field must match the format Y-m-d."
        ],
        "sort": [
            "The selected sort is invalid."
        ]
    }
}

Καταστάσεις αποστολής (status) ​

Κάθε αποστολή έχει ένα status (σταθερή τιμή για κώδικα) και ένα status_label (κείμενο στα Ελληνικά για εμφάνιση). Το status είναι και η τιμή του φίλτρου status στο GET /shipments.

statusstatus_labelΣημασία
cancelledΑκυρωμένηΤο voucher ακυρώθηκε.
returnedΕπιστράφηκεΗ αποστολή επιστράφηκε στον αποστολέα.
deliveredΠαραδόθηκεΠαραδόθηκε στον παραλήπτη.
lostΧάθηκεΗ αποστολή δηλώθηκε χαμένη.
problemΠρόβλημαΗ αποστολή έχει σημανθεί ως προβληματική (π.χ. αδυναμία παράδοσης).
stalledΧωρίς κίνησηΔεν υπάρχει νέα κίνηση για μεγάλο διάστημα.
in_transitΣε μεταφοράΗ courier παρέλαβε την αποστολή.
createdΔημιουργήθηκεΈχει εκδοθεί voucher, αλλά δεν υπάρχει ακόμα παραλαβή.

Προτεραιότητα. Η κατάσταση προκύπτει από ενδείξεις (flags) της αποστολής και μπορεί να ισχύουν περισσότερες από μία ταυτόχρονα (π.χ. μια αποστολή που παραλήφθηκε και μετά παραδόθηκε έχει και in_transit και delivered). Εμφανίζεται πάντα η πρώτη που ισχύει με τη σειρά του πίνακα (από πάνω προς τα κάτω): cancelled > returned > delivered > lost > problem > stalled > in_transit. Αν δεν ισχύει καμία, η κατάσταση είναι created. Το φίλτρο status ακολουθεί τον ίδιο κανόνα: το status=in_transit επιστρέφει μόνο αποστολές που εμφανίζονται ως in_transit, όχι και τις παραδομένες.

Ημερομηνίες και ποσά ​

  • Ημερομηνίες: ISO 8601 σε UTC με κατάληξη Z, π.χ. 2026-10-15T05:40:00Z (σε όλα τα endpoints και τα webhook payloads, και στα webhook endpoints / deliveries). Τα φίλτρα created_from / created_to (αποστολές) και ordered_from / ordered_to (παραγγελίες) είναι ημερομηνίες Y-m-d, στη ζώνη ώρας Europe/Athens.
  • Ποσά: πάντα string με 2 δεκαδικά και τελεία, π.χ. "49.90", ώστε να μην χάνεται ακρίβεια. Το νόμισμα είναι EUR. Το cod_amount είναι "0.00" όταν η αποστολή δεν έχει αντικαταβολή, και null μόνο όταν δεν έχει αποθηκευτεί ποσό για την αποστολή. Εξαίρεση στα 2 δεκαδικά: το amount των κινήσεων υπολοίπου έχει 4 δεκαδικά, γιατί οι τιμές SMS και voucher μπορεί να είναι κάτω από ένα λεπτό.

Αριθμός παραγγελίας και παλαιότερες παραγγελίες ​

Κάθε αποστολή ανήκει σε μια παραγγελία (order) με τρία αναγνωριστικά:

ΠεδίοΠεριγραφή
order.idΤο εσωτερικό ID της παραγγελίας στο CourierHub.
order.order_idΤο ID της παραγγελίας στο e-shop (π.χ. το εσωτερικό ID του Shopify, 5012345678901), ως string. Για παραγγελίες που δημιουργήθηκαν από το API ή χειροκίνητα, αριθμός που παράγει το CourierHub.
order.order_numberΟ αριθμός παραγγελίας όπως τον βλέπει ο πελάτης, π.χ. #12345. Μπορεί να είναι null.

Για παραγγελίες Shopify, το order_number (#12345) αποθηκεύεται για όλες τις νέες παραγγελίες, αλλά για τις παλαιότερες συμπληρώθηκε εκ των υστέρων (backfill) μόνο για τις τελευταίες 60 ημέρες. Παραγγελίες παλαιότερες των 60 ημερών μπορεί να μην έχουν order_number: αναζητήστε τες με το εσωτερικό ID της Shopify (order.order_id) στο φίλτρο order_number του GET /shipments, που δέχεται και τις δύο μορφές.

Παραγγελίες ​

Με τα endpoints /orders το σύστημά σας δημιουργεί παραγγελίες στο CourierHub (π.χ. από ERP ή από e-shop χωρίς έτοιμη διασύνδεση), ώστε να εκδοθεί για αυτές voucher όπως για κάθε άλλη παραγγελία: με POST /shipments (order_id = το id της παραγγελίας) ή από το περιβάλλον πελάτη.

  • Εύρος. Η ανάγνωση, η ενημέρωση και η διαγραφή καλύπτουν τις παραγγελίες όλων των καταστημάτων του λογαριασμού σας (και αυτές που ήρθαν από e-shop). Οι νέες παραγγελίες δημιουργούνται στο κατάστημα του token.
  • Χωρίς συγχρονισμό με e-shop. Οι παραγγελίες που δημιουργούνται ή αλλάζουν από το API είναι παραγγελίες του CourierHub: δεν στέλνονται σε e-shop (Shopify, WooCommerce κ.λπ.). Ούτε η έκδοση voucher για παραγγελία που δημιουργήθηκε από το API ενημερώνει κάποιο e-shop.
  • Με voucher δεν αλλάζουν. Μια παραγγελία με voucher (οποιοδήποτε μη διαγραμμένο voucher, και αυτό που εκδίδεται εκείνη τη στιγμή) δεν αλλάζει ούτε διαγράφεται: η απάντηση είναι 409 order_has_shipment. Πρώτα πρέπει να ακυρωθεί το voucher, με DELETE /shipments/{voucher} ή από το περιβάλλον πελάτη (Αποστολές & Vouchers). Δείτε PATCH /orders/{id}.

order_id και order_number ​

ΠεδίοΠοιος το ορίζειΠεριγραφή
idCourierHubΤο εσωτερικό ID της παραγγελίας. Χρησιμοποιείται στα URLs /orders/{id}.
order_idCourierHubΑριθμητικό αναγνωριστικό που παράγει το CourierHub στη δημιουργία (π.χ. 20261015101200482) και δεν αλλάζει ποτέ. Είναι η αναφορά παραγγελίας που λαμβάνει η courier. Για παραγγελίες από e-shop είναι το ID του e-shop.
order_numberΕσείςΟ δικός σας αριθμός παραγγελίας, υποχρεωτικός στη δημιουργία. Επιτρέπονται γράμματα και ψηφία (π.χ. #1001, INV-2026/17). Είναι μοναδικός ανάμεσα στις μη διαγραμμένες παραγγελίες του καταστήματος: μια διαγραμμένη παραγγελία ελευθερώνει τον αριθμό της. Τα #1001 και 1001 θεωρούνται ο ίδιος αριθμός. Σε διαφορετικά καταστήματα ο ίδιος αριθμός επιτρέπεται.

Το φίλτρο order_number του GET /orders και του GET /shipments δέχεται και το order_number (με ή χωρίς #) και το order_id.

Endpoints ​

ΣελίδαEndpoint
Λίστα παραγγελιώνGET /orders
Στοιχεία παραγγελίαςGET /orders/{id}
Δημιουργία παραγγελίαςPOST /orders
Ενημέρωση παραγγελίαςPATCH /orders/{id}
Διαγραφή παραγγελίαςDELETE /orders/{id}
Λίστα αποστολώνGET /shipments
Στοιχεία αποστολήςGET /shipments/{voucher}
Tracking αποστολήςGET /shipments/{voucher}/tracking
Δημιουργία αποστολήςPOST /shipments
Ακύρωση αποστολήςDELETE /shipments/{voucher}
Ετικέτα αποστολής (PDF)GET /shipments/{voucher}/label
Webhooks (γεγονότα, υπογραφή, retries)-
Διαχείριση webhook endpoints/webhook-endpoints
ΛογαριασμόςGET /account
CourierGET /couriers
Κινήσεις υπολοίπουGET /balance/transactions

Released under the MIT License.