curl -X POST 'https://api.cyclemate.club/custom-routes/78360cf7-0f4a-48f1-91ca-25e83db44aff/images/' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-F '[email protected]'
{
"id": "3f8e3b35-25c5-48bb-a5ab-486ad4c6a5a1",
"url": "https://cyclemate.club/storage/images/event-78360cf7-0f4a-48f1-91ca-25e83db44aff/1792536705772-fa691fa8.jpg",
"status": "pending",
"cover": true
}
Custom routes
Upload an image
Add a photo to a route you host
curl -X POST 'https://api.cyclemate.club/custom-routes/78360cf7-0f4a-48f1-91ca-25e83db44aff/images/' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-F '[email protected]'
{
"id": "3f8e3b35-25c5-48bb-a5ab-486ad4c6a5a1",
"url": "https://cyclemate.club/storage/images/event-78360cf7-0f4a-48f1-91ca-25e83db44aff/1792536705772-fa691fa8.jpg",
"status": "pending",
"cover": true
}
Overview
Adds one image to the route and answers201 with it. The first image is the cover. Every image goes through our image check before anyone but the hosts sees it: it answers status: "pending" until then. Files are stored as uploaded; strip location metadata before uploading a rider’s photo.
Authentication
A Login with Cyclemate access token, or a signed-in rider’s session token. Every route belongs to a rider: your app acts for the rider who connected it, and the route is theirs. Your app reaches only the routes it created: a route the rider made in our apps, or through another app, answers404.
Authorization: Bearer ACCESS_TOKEN
Rate limits
Riders and apps acting for them may make 60 requests a minute; a request that routes betweenstops counts once per hop. Every rider may create 10 routes in any 24 hours, counted across our apps and every connected app: the eleventh answers 429 with code: "daily_limit" and the seconds until a slot frees.
Path parameters
string
required
The route’s id.
Request body
Amultipart/form-data body with one part:
file
required
A JPEG, PNG or WebP under 10 MiB. The type is read from the file’s bytes, not its name. A route holds at most 10 images.
Response body
string
The image’s id.
string
The image’s public URL.
string
pending until the image check has run.boolean
Whether this image is the route’s cover.
curl -X POST 'https://api.cyclemate.club/custom-routes/78360cf7-0f4a-48f1-91ca-25e83db44aff/images/' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-F '[email protected]'
{
"id": "3f8e3b35-25c5-48bb-a5ab-486ad4c6a5a1",
"url": "https://cyclemate.club/storage/images/event-78360cf7-0f4a-48f1-91ca-25e83db44aff/1792536705772-fa691fa8.jpg",
"status": "pending",
"cover": true
}
Variants
Choosing the cover
The first image uploaded is the cover. To change it, sendcover_image_id to PATCH /custom-routes/{id}/.
Error responses
Every error body is{ "error", "code" }; error is a sentence, code is stable.
400 Bad request
Invalid body or query: the message names the field.code is invalid_body, or for a route input outside_coverage (the route starts outside the cities we cover), cross_city (its stops span two cities) or no_route (no cycling route exists between two stops).
{ "error": "unknown field: colour", "code": "invalid_body" }
401 Unauthorized
Missing or invalid credentials, or an anonymous caller.{ "error": "Missing user credentials" }
404 Not found
No route with that id that you may see. A private route you do not host, a route you may read but not edit, and a route your app did not create all answer404, never 403.
{ "error": "Custom route not found", "code": "not_found" }
405 Method not allowed
{ "error": "Method not allowed", "code": "method_not_allowed" }
413 Payload too large
The file is over 10 MiB.{ "error": "Images must be under 10 MiB", "code": "payload_too_large" }
415 Unsupported media type
Not a JPEG, PNG or WebP, or not sent as multipart form data.{ "error": "Images must be JPEG, PNG or WebP", "code": "unsupported_media_type" }
429 Too many requests
The burst limit, with aRetry-After header; or the daily creation cap, which adds code: "daily_limit".
{ "error": "Daily limit of 10 custom routes reached", "code": "daily_limit", "retry_after_seconds": 3600 }
500 Internal server error
{ "error": "Internal server error", "code": "internal_error" }