Εμφάνιση
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_page | Integer | Πόσα αποτελέσματα ανά σελίδα, από 1 έως 100. Προεπιλογή 50. |
cursor | String | Αδιαφανές (opaque) token που δίνεται από την προηγούμενη σελίδα. Μην το τροποποιείτε. |
Η απάντηση έχει πάντα τη μορφή {data, links, meta}:
| Πεδίο | Τύπος | Περιγραφή |
|---|---|---|
data | Array | Τα αποτελέσματα της σελίδας. |
links.next | String ή null | Πλήρες URL για την επόμενη σελίδα (κρατάει τα φίλτρα και το per_page). null στην τελευταία σελίδα. |
links.prev | String ή null | Πλήρες URL για την προηγούμενη σελίδα. |
meta.per_page | Integer | Το μέγεθος σελίδας που εφαρμόστηκε. |
meta.next_cursor | String ή null | Το cursor της επόμενης σελίδας. |
meta.prev_cursor | String ή 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.
code | HTTP | Πότε |
|---|---|---|
unauthenticated | 401 | Λείπει το Bearer token ή δεν είναι έγκυρο (π.χ. webhook token αντί για API Token). |
forbidden | 403 | Δεν επιτρέπεται η ενέργεια. |
not_found | 404 | Ο πόρος δεν υπάρχει, δεν ανήκει στον λογαριασμό σας, ή το URL δεν υπάρχει. |
method_not_allowed | 405 | Λάθος HTTP method για το URL. Η κεφαλίδα Allow λέει ποια επιτρέπονται. |
order_number_taken | 409 | Το order_number το χρησιμοποιεί ήδη άλλη παραγγελία του καταστήματος (POST / PATCH /orders). |
order_has_shipment | 409 | Η παραγγελία έχει voucher και δεν αλλάζει ούτε διαγράφεται (PATCH / DELETE /orders/{id}). |
validation_failed | 422 | Μη έγκυρες παράμετροι. Το errors έχει τα προβλήματα ανά πεδίο. |
rate_limited | 429 | Υπέρβαση του ορίου κλήσεων. Δείτε το Retry-After. |
server_error | 5xx | Σφάλμα στην πλευρά του CourierHub. Δοκιμάστε ξανά αργότερα. |
client_error | άλλα 4xx | Οποιοδήποτε άλλο σφάλμα πελάτη (π.χ. 400, 413). |
Επιπλέον κωδικοί των κλήσεων έκδοσης, ακύρωσης και ετικέτας αποστολής:
code | HTTP | Πότε |
|---|---|---|
shipment_limit_reached | 422 | Έχετε φτάσει το όριο αποστολών της τρέχουσας περιόδου. |
insufficient_balance | 422 | Το διαθέσιμο υπόλοιπο δεν καλύπτει το κόστος του voucher. |
courier_not_connected | 422 | Δεν έχετε συνδέσει λογαριασμό με την courier. |
courier_not_supported | 422 | Η courier δεν υποστηρίζει vouchers μέσω CourierHub. |
courier_validation_failed | 422 | Η courier απέρριψε τα στοιχεία της παραγγελίας. Το errors έχει τα προβλήματα ανά πεδίο. |
courier_cancellation_unsupported | 422 | Η courier δεν υποστηρίζει ακύρωση μέσω API (όλες εκτός από ACS, BoxNow και ELTA). |
shipment_in_progress | 409 | Άλλη κλήση εκδίδει αυτή τη στιγμή voucher για την ίδια παραγγελία και courier. Δείτε το Retry-After. |
courier_error | 502 | Η courier απέτυχε. Το message περιγράφει το σφάλμα, άλλοτε με το μήνυμα της ίδιας της courier και άλλοτε με δική μας περιγραφή (π.χ. No voucher number returned from ACS API). Είναι για ανθρώπους: για τον χειρισμό στηριχτείτε στο code. |
label_unavailable | 502 | Η courier δεν επέστρεψε αρχείο ετικέτας. |
voucher_charge_failed | 500 | Το 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.
status | status_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
| Πεδίο | Ποιος το ορίζει | Περιγραφή |
|---|---|---|
id | CourierHub | Το εσωτερικό ID της παραγγελίας. Χρησιμοποιείται στα URLs /orders/{id}. |
order_id | CourierHub | Αριθμητικό αναγνωριστικό που παράγει το 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 |
| Courier | GET /couriers |
| Κινήσεις υπολοίπου | GET /balance/transactions |