> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyclemate.club/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload an image

> Add a photo to a route you host

## Overview

Adds one image to the route and answers `201` 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](https://docs.cyclemate.club/api-reference/oauth.md) 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, answers `404`.

```
Authorization: Bearer ACCESS_TOKEN
```

An API key on its own is rejected, as are anonymous requests. To create routes as your organisation, sign up for a Cyclemate account and connect your app to it with Login with Cyclemate.

### Rate limits

Riders and apps acting for them may make **60 requests a minute**; a request that routes between `stops` 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

**id** `string` *required*

The route's id.

## Request body

A `multipart/form-data` body with one part:

**file** `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

**id** `string`

The image's id.

**url** `string`

The image's public URL.

**status** `string`

`pending` until the image check has run.

**cover** `boolean`

Whether this image is the route's cover.

```bash cURL theme={null}
curl -X POST 'https://api.cyclemate.club/custom-routes/78360cf7-0f4a-48f1-91ca-25e83db44aff/images/' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -F 'file=@bakery.jpg'
```

```json Response theme={null}
{
  "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, send `cover_image_id` to [`PATCH /custom-routes/{id}/`](https://docs.cyclemate.club/api-reference/custom-routes/update-route).

## 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).

```json theme={null}
{ "error": "unknown field: colour", "code": "invalid_body" }
```

### 401 Unauthorized

Missing or invalid credentials, or an anonymous caller.

```json theme={null}
{ "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 answer `404`, never `403`.

```json theme={null}
{ "error": "Custom route not found", "code": "not_found" }
```

### 405 Method not allowed

```json theme={null}
{ "error": "Method not allowed", "code": "method_not_allowed" }
```

### 413 Payload too large

The file is over 10 MiB.

```json theme={null}
{ "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.

```json theme={null}
{ "error": "Images must be JPEG, PNG or WebP", "code": "unsupported_media_type" }
```

### 429 Too many requests

The burst limit, with a `Retry-After` header; or the daily creation cap, which adds `code: "daily_limit"`.

```json theme={null}
{ "error": "Daily limit of 10 custom routes reached", "code": "daily_limit", "retry_after_seconds": 3600 }
```

### 500 Internal server error

```json theme={null}
{ "error": "Internal server error", "code": "internal_error" }
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.