API
- Introduction
- Changelog
- Environments
- Authentication protocol
- Entities
- API Requests
API version 2
Introduction
Cyke is the homebrewed delivery order tool of Cargonautes (ex OLVO). In addition to a graphical interface, it has a simple RESTful API to manage deliveries, targetted mainly at e-commerce websites.
If you’re here, it’s probable your company has already started opening an account with Cargonautes (ex OLVO) (or another delivery company using Cyke) and you already have a precise idea of the delivery services that you’ll need. If it’s not the case, please write to contact@cargonautes.fr. If it’s the case, we’ll arrange by email an account on our integration platform (please don’t test the API on production, as we’ll have real people pedaling behind and you’ll be billed!)
The API has (for now) five endpoints to handle your deliveries. It is designed to avoid time-consuming operations when creating deliveries. But for day-to-day management of deliveries, it needs to be coupled to the Cyke graphical interface, where other operations (like understanding billing) are possible.
In addition to these entry endpoints, Cyke also has outgoing webhooks, documented here that can inform your systems back at each delivery change.
Feature requests are very welcomed, feel free to write to cyke@cargonautes.fr for that.
Specificities for e-commerce websites
Cyke offers direct integrations with the main e-commerce platforms. You may use the Shopify and Woocommerce plugins in this documentation.
In comparison to other delivery APIs, Cyke has more required information.
The main difference is that we expect your website to capture and send a precise delivery slot, with a slot start and a slot end (e.g. 2021-01-20 15:00:00
/ 2021-01-20 18:00:00
). It’s your responsability to capture the day and slot of delivery, and communicate it to your customer. Most e-commerce CMS have available plugins in their marketplace to propose and capture this kind of slots, but we can advise plugins for Shopify and Woocommerce if needed.
Please also note that a phone number is a requirement, and that we strongly encourage to capture access information such as entrance code, floor, apartment number, etc, to keep delivery failure rate low.
Zapier plugin (compatible with CMS…)
Cyke offers a zero-code integration that works with the main e-commerce and CMS platforms: Shopify, Wordpress, Woocommerce, Prestashop… This integration is dealt through a generic Zapier/Cyke application. For now this Zapier plugin is kept private and shared on demand. If you know how to use a Zapier plugin, you can ask an access. If you don’t know how to use a Zapier plugin, we can take care of connecting for you your e-commerce website to Cyke with this plugin, and monitor the integration. Feel free to contact your sales representative if interested.
Changelog
Upstream changes
These changes have been released directly on the current version of the API because they preserve backwards compatibility.
- The package object has a new
client_reference
field to store any ID on the client side, eg. a barcode. - The package object contains all the usefull information to describe it:
name
,volume
,weight
,temperature
andfragility
- The Package object can be created providing the
name
,volume
andweight
instead of thepackage_type_id
Migrating from v1
V2 has been released on July 10th, 2023. The API v1 has been shut down on May 23rd, 2024.
Here are the main changes:
- Timeslots:
complete_after
andcomplete_before
are no longer used on the delivery. Please useslot_starting_at
andslot_ending_at
on the tasks (pickup and/or dropoff) instead. - Packages are no longer sent by their names but by id. You may retrieve your package types list using the API.
- The API is no longer available at pedalo.olvo.fr, please use www.cyke.io
If you need to have a look at the API v1, you can access the documentation there.
Environments
The Cyke API es available directly on the Cyke main production server at https://www.cyke.io/.
If you need to develop an API integration and do not want your tests to interfere with the production data, you may ask for an access to a test environment. Please send us an email at cyke@cargonautes.fr.
Test server specific features:
- It is available at https://staging.cyke.io/
- It never send emails nor text messages since deliveries are fake. (This means you cannot ask for a new password using the “Forgotten password feature”)
Authentication protocol
The authentification is done by providing an email and a token in the HTTP header of your request:
X-User-Email: <EMAIL>
X-User-Token: <API_TOKEN>
For example it could be something like:
X-User-Email: victor@my_company.com
X-User-Token: G6RNXfdNaPPh3DtMYAR8
You can get your API token in the general information page in your Cyke Account. From the sidebar: Configuration > General information, and scroll down to the API section to reveal your token.
Entities
Delivery
A Delivery represents the main service sold by Cargonautes (ex OLVO): moving things around from one place to another, during defined timeslots.
- The “things” moved around are Packages
- A Delivery is composed of two tasks, a Pickup task, and a Dropoff task. Each task has its own timeslots – slot_starting_at and slot_ending_at
- These Tasks happen at specific Places, that have an address, a recipient, etc.
Cyke has the concept of «default place» for a customer: it defines where you are hosted and where your outbound deliveries are sent from. It can be your own office/store, or it can be a hub owned by Cargonautes (ex OLVO) (in this case, you are responsible for bringing the packages to Cargonautes (ex OLVO).) Task details happening at this default place (usually a Pickup task), be it your own place or Cargonautes (ex OLVO) hub, do not need to be specified in the API creation call and will be automatically filled. When creating a Delivery through the API, you should only specify the details of the Tasks and Places that do happen at the final recipient place (your own client). That means that for most of the Deliveries, you only send a Dropoff task and its place. If you need an inbound Delivery where a messenger picks up a package and brings it to you, you’ll need only to specify a Pickup object and Place.
The delivery time slot is represented by two fields, slot_starting_at
and slot_ending_at
on the Tasks. As only the main Task (usually a dropoff) needs to be specified, the timeslot of the Task happening at your own place is automatically calculated. The API accepts most timeslots, unless they are already started. It is the client’s responsibility to implement the business rules linked to the desired time slot size, anticipation time, and opening hours and days – knowing that reduced slot size and anticipation can induce higher prices.
Attributes:
Name | Type | Description |
---|---|---|
sender_display_name |
string |
Optional. If you want to deliver a parcel in the name of another name than yours, you can write it here. If given, this is the name that the recipient will see in the SMS that is sent to him, instead of your company’s name. |
comments |
string |
Optional. Any comments you could make about the delivery to help our staff. |
client_order_reference |
string |
Optional. An internal order reference of yours, ( typically your own purchase id for this delivery). You can later retrieve a delivery using this reference. |
bring_back_containers |
boolean |
Optional. If the messenger needs to bring back deposited containers (empty bottle, etc). If not specified, the company default is applied. |
dropoff |
Task |
Optional as long as you give a pickup. The address and recipient information where to deliver. |
pickup |
Task |
Optional as long as you give a dropoff. The address and recipient information where to pick the goods up. |
packages |
array of Packages |
Should at least contain one Package. The packages that your delivery contains. When updating packages, you must provide the whole array of packages as it will override the existing packages. Refer to the Package and PackageType entities and the code example below to see what is expected from a package. |
reversed_packages |
array of Packages |
Only taken into account if bring_back_containers param is used. The packages that should be collected upon delivery. |
When getting an existing Delivery, you will also get access to additionnal fields:
Name | Type | Description |
---|---|---|
status |
string enum:saved ,scheduled ,picked_up ,delivered ,cancelled ,failed
|
The status of the delivery. saved means the delivery has been saved to our dispatch system and is currently not assigned to anybody. scheduled means the task has been assigned to one of our workers (the worker may not have started the task yet, though). picked_up is for deliveries composed of one pickup and one dropoff: it means the goods have been picked up by us, and are now waiting to be delivered. If the goods have been successfully delivered, the status is delivered , otherwise if the delivery failed, the status is failed . If the task has been cancelled, the status is cancelled . |
price_cents |
integer | If we include some pricing functionality for your company in Cyke, the price_cents would show the price of the delivery in cents. Otherwise, it will be equal to null . |
direction |
string enum:outbound ,inbound ,other
|
Tells if the delivery’s main task is the dropoff (outbound ), the pickup (inbound ), or both the pickup and the dropoff (other ). |
original_delivery_id |
integer |
Optional. Identifier of the original delivery if this delivery is a redelivery. |
redelivery_id |
integer |
Optional. Identifier of the redelivery if this delivery has one. |
Task
A Task can be either a saved address that you have already in your account, or a place. In the first case you should give the Task’s unique number in this field:
{
...
"pickup": 23,
...
}
Otherwise, juste give a JSON with an argument place
inside:
{
...
"pickup": {
"slot_starting_at": "2019-09-04T16:00:00.000+02:00",
"slot_ending_at": "2019-09-04T18:00:00.000+02:00",
"place": {
"recipient_name": "Cercei Lannister",
"recipient_phone": "+33670707070",
"address": "1 place du Palais Royal",
"postal_code": "75001",
"city": "Paris",
}
},
...
}
When getting an existing Task, you will also get access to the following fields:
Name | Type | Description |
---|---|---|
slot_starting_at |
string |
Mandatory. The time after which the task should be completed. Has to be written in GMT format. Example: “2020-11-09T16:00:00.000+02:00” |
slot_ending_at |
string |
Mandatory. The time before which the task should be completed. Has to be written in GMT format. Example: “2020-11-09T18:00:00.000+02:00” |
tracking_url |
string | A public url your customers can use to track the messenger delivering their packages. Available once the delivery has started |
completed_at |
string | The datetime at which the delivery has been completed (delivered of failed )Example: “2020-11-09T18:00:00.000+02:00”” |
failure_reason |
string | The reason why the delivery task failed. Chosen by the messenger in their application. |
notes |
string | Notes filled freely by the messenger when they completed the task. |
place |
Place | See below at Place |
completion_signature |
Object | A url to access the recipient signature and accepted_by containing their name. Available once the delivery has been delivered |
completion_pictures |
Array | A collection of url to access the pictures. Available once the delivery has been delivered |
Place
A Place is basically an address with some recipient information:
Attributes:
Name | Type | Description |
---|---|---|
recipient_name |
string |
Mandatory. The recipient’s name and surname. When shipping to a company, give the operations manager’s name and phone. |
recipient_phone |
string |
Mandatory, except for some particular cases (contact us for more information). The recipient’s phone number. International format is strongly recommended. A tracking link will be sent to this number when the courier starts the task. Example: “+32485549268” |
recipient_email |
string |
Optional. The recipient’s email address. In the future, a tracking link may be sent to this address before the courier arrives. Example: “tartempion@example.org” |
company_name |
string |
Optional. The company’s name when shipping to an office, a shop, etc. |
address |
string |
Mandatory. The number and street name. Don’t give any other information here (it will fail otherwise). Instead, keep them for address_instructions .Example: “1 place du Palais Royal” |
postal_code |
string |
Mandatory. The postal code of the address. Should be in the zone that we deliver in. Please contact us so we can give you the list o accepted locations. Example: “75000” |
city |
string |
Mandatory. The city name. |
address_instructions |
string |
Optional but highly recommended. Any information that could help the courier reach the recipient’s front door. Access code, building number, interphone, alley, floor, apartment number, digicode, etc. Example: « digicode 3495 on your left inside.”_ |
Package
A Package represents a parcel or any kind of merchandise that is to be transported on a Delivery.
Attributes:
Name | Type | Description |
---|---|---|
amount |
integer |
Mandatory. The amount of packages of this kind in the delivery. |
package_type_id |
integer | The identifier of the package type on Cyke. |
name |
string | A name you can provide to identify and describe the package. |
volume_dm3 |
float | The quantity represented by the package. In liters. |
weight_kg |
float | The weight represented by the package. In kilograms. |
fragility |
enum |
null , fragile or super_fragile . |
temperature |
enum |
null , fresh or hot . |
client_reference |
string | An identifier of the package. Can be the barcode. |
When creating a delivery, you will have to provide either:
-
volume_dm3
,weight_kg
andname
to provide directly the mandatory information OR - A
package_type_id
to use the information from the PackageType
Note: In previous version of the API, you were able to describe a package with its type
only, without id. This is no longer the recommended way.
PackageType
PackageTypes are examples of packages that you can use to describe the delivery packages without providing all the data for each package.
Each customer account has a list of accessible package types or size categories. PackagesTypes will describe objects that are delivered (ie. “Bottle 1.5L”, “Flower bunch size M”, etc.), while size categories will rather describe the size of a delivery – not what’s inside (ie. “small parcel < 12L”, “medium parcel < 15L”, etc.).
Package types must be agreed beforehand commercially, and configured in the Cyke staging and production environment before you can use them.
Attributes:
Name | Type | Description |
---|---|---|
id |
integer |
Mandatory. The identifier of the package type on Cyke. |
type |
string | The exact name of the package type, as you can see it in your Cyke account. |
volume |
float | The quantity represented by the package type. In liters. |
weight |
float | The weight represented by the package type. In kilograms. |
fragility |
enum |
null , fragile or super_fragile . |
temperature |
enum |
null , fresh or hot . |
API Requests
The Cyke API allows you to either list your deliveries, create a delivery, get a delivery, edit a delivery or cancel a delivery.
List package types
Endpoint
GET /api/v2/package_types
URL Parameters
None.
Code examples
Request
curl --location --request GET '<URL>/api/v2/package_types' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
Response
[
{
"id": 1,
"type": "Carton de 12 bières 33cl",
"volume": 4,
"weight": 6,
"fragility": "fragile",
"temperature": null
},
...
{
"id": 5,
"type": "Fûts inox de 20L",
"volume": 25,
"weight": 30,
"fragility": null,
"temperature": null
}
]
List deliveries
Endpoint
GET /api/v2/deliveries
URL Parameters
Parameter | Type | Examples | Description |
---|---|---|---|
client_order_reference |
string |
client_order_reference=PO5EF29A |
Filter deliveries having a specific client order reference. |
Code examples
Request
curl --location --request GET '<URL>/api/v2/deliveries' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
If you want to apply specific filters:
curl --location --request GET '<URL>/api/v2/deliveries?client_order_reference=POE5F29A' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
Response
[
{
"id": 1,
"created_at": "2020-07-25T11:50:00.000+02:00",
"status": "delivered",
"slot_starting_at": "2020-08-01T11:00:00.000+02:00",
"slot_ending_at": "2020-08-01T13:00:00.000+02:00",
"price_cents": null
},
...
{
"id": 86,
"created_at": "2020-11-09T10:00:00.000+02:00",
"status": "scheduled",
"slot_starting_at": "2020-11-09T16:00:00.000+02:00",
"slot_ending_at": "2020-11-09T18:00:00.000+02:00",
"price_cents": null
}
]
Create a delivery
Endpoint
POST /api/v2/deliveries
Code examples
Request
curl --location --request POST '<URL>/api/v2/deliveries/' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--header 'Content-Type: application/json' \
--data-raw '{
"dropoff": {
"slot_starting_at": "2020-11-09T16:00:00.000+02:00",
"slot_ending_at": "2020-11-09T18:00:00.000+02:00",
"place": {
"recipient_name": "Cercei Lannister",
"recipient_phone": "+33670707070",
"company_name": "Lannister Inc.",
"address": "1 place du Palais Royal",
"postal_code": "75001",
"city": "Paris",
"address_instructions": "digicode 3495 au fond à gauche"
}
},
"packages": [
{
"name": "Carton de 12 bières 33cl",
"amount": 3,
"volume_dm3": 4,
"weight_kg": 6,
"fragility": null,
"temperature": null,
"client_reference": "1234"
},
{
"package_type_id": 5,
"amount": 1
}
],
"comments": "Fûts de blonde, faire attention à la DLUO",
"client_order_reference": "POE5F29A",
"bring_back_containers": false
}'
require "uri"
require "net/http"
require "json"
url = URI("https://<URL>/api/v2/deliveries")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-User-Email"] = "victor@my_company.com"
request["X-User-Token"] = "G6RNXfdNaPPh3DtMYAR8"
request["Content-Type"] = "application/json"
delivery = {
"dropoff": {
"slot_starting_at": "2020-11-09T16:00:00.000+02:00",
"slot_ending_at": "2020-11-09T18:00:00.000+02:00",
"place": {
"recipient_name": "Cercei Lannister",
"recipient_phone": "+33670707070",
"company_name": "Lannister Inc.",
"address": "1 place du Palais Royal",
"postal_code": "75001",
"city": "Paris",
"address_instructions": "digicode 3495 au fond à gauche"
}
},
"packages": [
{
"name": "Carton de 12 bières 33cl",
"amount": 3,
"volume_dm3": 4,
"weight_kg": 6,
"fragility": null,
"temperature": null,
"client_reference": "1234",
},
{
"package_type_id": 5,
"amount": 1
}
],
"comments": "Fûts de blonde, faire attention à la DLUO",
"client_order_reference": "POE5F29A",
"bring_back_containers": false
}
request.body = delivery.to_json
response = https.request(request)
puts response.read_body
Response
Returns the created Delivery with an http status of 201 (Created). See Get a delivery response.
If there is an error during the delivery creation, it will be returned instead of the delivery, and the http status will be 422 (Unprocessable Entity).
For example:
{
"errors": [
"Packages Unknown type"
]
}
Get a delivery
Endpoint
GET /api/v2/deliveries/<DELIVERY_ID>
Code examples
Request
curl --location --request GET '<URL>/api/v2/deliveries/86' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
Response
{
"id": 86,
"created_at": "2020-11-09T10:00:00.000+02:00",
"status": "saved",
"comments": "Fûts de blonde, faire attention à la DLUO",
"client_order_reference": "POE5F29A",
"price_cents": null,
"direction": "outbound",
"bring_back_containers": false,
"original_delivery_id": null,
"redelivery_id": null,
"dropoff": {
"slot_starting_at": "2020-11-09T16:00:00.000+02:00",
"slot_ending_at": "2020-11-09T18:00:00.000+02:00",
"tracking_url": null,
"completed_at": null,
"failure_reason": null,
"notes": null,
"completion_signature": null,
"completion_pictures": [],
"place": {
"recipient_name": "Cercei Lannister",
"recipient_phone": "+32 470 70 70 70",
"company_name": "Lannister Inc.",
"address": "1 place du Palais Royal",
"postal_code": "75001",
"city": "Paris",
"address_instructions": "digicode 3495 au fond à gauche"
}
},
"packages": [
{
"package_type_id": null,
"type": "Carton de 12 bières 33cl",
"name": "Carton de 12 bières 33cl",
"amount": 3,
"volume_dm3": 4,
"weight_kg": 6,
"fragility": null,
"temperature": null,
"client_reference": "1234"
},
{
"package_type_id": 5,
"type": "Thermobox",
"name": "Thermobox",
"amount": 1,
"volume_dm3": 20,
"weight_kg": 10,
"fragility": null,
"temperature": "fresh",
"client_reference": null
}
],
"reversed_packages": []
}
Get the latest delivery
This endpoint retrieves the latest delivery for the given filters.
Endpoint
GET /api/v2/deliveries/latest
URL Parameters
Parameter | Type | Examples | Description |
---|---|---|---|
client_order_reference |
string |
client_order_reference=PO5EF29A |
Filter deliveries having a specific client order reference. |
Code examples
Request
curl --location --request GET '<URL>/api/v2/deliveries/latest' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
If you want to apply specific filters:
curl --location --request GET '<URL>/api/v2/deliveries/latest?client_order_reference=POE5F29A' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--data-raw ''
Response
Returns the updated Delivery with an http status of 200 (Created). See Get a delivery response.
Update a delivery
Endpoint
PATCH /api/v2/deliveries/<DELIVERY_ID>
PUT /api/v2/deliveries/<DELIVERY_ID>
Code examples
Request
curl --location --request PUT '<URL>/api/v2/deliveries/<DELIVERY_ID>' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--header 'Content-Type: application/json' \
--data-raw '{
"dropoff": {
"slot_starting_at": "2020-11-09T16:30:00.000+02:00"
}
}'
require "uri"
require "net/http"
require "json"
url = URI("https://<URL>/api/v2/deliveries/<DELIVERY_ID>")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Put.new(url)
request["X-User-Email"] = "victor@my_company.com"
request["X-User-Token"] = "G6RNXfdNaPPh3DtMYAR8"
request["Content-Type"] = "application/json"
delivery = {
"dropoff": {
"slot_starting_at": "2020-11-09T16:30:00.000+02:00"
},
}
request.body = delivery.to_json
response = https.request(request)
puts response.read_body
Response
Returns the updated Delivery with an http status of 200 (Created). See Get a delivery response.
If there is an error during the delivery update, it will be returned instead of the delivery, and the http status will be 422 (Unprocessable Entity).
For example:
{
"errors": [
"Packages Unknown type"
]
}
Cancel a delivery
Endpoint
PATCH /api/v2/deliveries/<DELIVERY_ID>/cancel
Code examples
Request
curl --location --request PATCH '<URL>/api/v2/deliveries/<DELIVERY_ID>/cancel' \
--header 'X-User-Email: victor@my_company.com' \
--header 'X-User-Token: G6RNXfdNaPPh3DtMYAR8' \
--header 'Content-Type: application/json'
require "uri"
require "net/http"
require "json"
url = URI("https://<URL>/api/v2/deliveries/<DELIVERY_ID>")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-User-Email"] = "victor@my_company.com"
request["X-User-Token"] = "G6RNXfdNaPPh3DtMYAR8"
request["Content-Type"] = "application/json"
response = https.request(request)
puts response.read_body
Response
Returns the canceled Delivery with an http status of 200 (Created). See Get a delivery response.
If there is an error during the delivery cancellation, it will be returned instead of the delivery, and the http status will be 422 (Unprocessable Entity).
For example:
{
"errors": [
"Packages Unknown type"
]
}