# Conventions

> Errors, money, ids and dates, paging, idempotency, rate limits, languages and versioning: how every endpoint behaves.

## Base URLs

| Environment | URL | Keys |
|---|---|---|
| Test | `https://api.staging.mongrl.com` | `sk_test_…`, `pk_test_…` |
| Live | `https://api.mongrl.com` | `sk_live_…`, `pk_live_…` |

All requests and responses are JSON (`application/json`), except photo uploads (the image's bytes). See [Authentication](https://dev.mongrl.com/guides/authentication.md) for keys and sessions.

## Errors

Errors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problems, sent as `application/problem+json`:

```json
{
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "the listing isn't ready to go on sale",
  "errors": [
    { "location": "body.price", "message": "a price is needed", "value": null },
    { "location": "body.images", "message": "add at least 3 photos" }
  ]
}
```

- Show `detail` to people. It's written to be read.
- `errors[].location` points at the field: `body.<field>`, `query.<name>` or `path.<name>`. Map it onto your form.
- Validation errors list **every** problem at once, not just the first.

| Status | Meaning |
|---|---|
| `400` | The request is malformed (bad JSON, a bad `Idempotency-Key`). |
| `401` | No key, a key that doesn't work, or a user session that has ended. |
| `403` | The key or user may not do this (e.g. a blocked user). |
| `404` | Not found, or not yours. Other marketplaces' and users' things are always `404`. |
| `409` | Not possible in the current state: sold, already in use, a transition that isn't available. |
| `422` | The input is wrong. See `errors`. |
| `429` | Too many attempts. Wait for `Retry-After` seconds. |
| `503` | A dependency (such as payments) isn't available. Try again later. |

## Money

Money is always an object with an integer amount in **minor units** and an ISO 4217 currency:

```json
{ "amount": 125000, "currency": "SEK" }
```

That's 1 250,00 kr. Never use floats for money. Totals and line items on orders are plain integers in the order's `currency`.

## Ids and dates

- Ids are UUIDs (`"8f3c2a1e-…"`). Treat them as opaque strings.
- Times are RFC 3339 in UTC (`"2026-10-19T14:05:00Z"`). Dates without a time are `YYYY-MM-DD`.

## Paging

Two styles, depending on the list:

| Lists | Style |
|---|---|
| Listings, favourites, a seller's listings, catalogue terms | **Cursor:** pass `next_cursor` back as `cursor`. No `next_cursor` means the last page. |
| Orders, conversations | **Offset:** `limit` and `offset`. A page shorter than `limit` is the last. |
| Messages in a conversation | **After:** `after=<message id>` returns only newer messages, oldest first. |

`limit` is at most 100 (200 for catalogue terms and messages). Search also returns `total`, the number of matches.

## Idempotency

Send `Idempotency-Key` on any `POST`, `PUT`, `PATCH` or `DELETE` you might retry, above all placing an order. Use a fresh UUID per user action.

- A retry with the same key and the same request doesn't run again: it replays the first response, with `Idempotent-Replayed: true`.
- The same key with a different request is `422`; while the first request still runs it's `409`.
- Server errors (`5xx`) aren't stored, so retrying them runs the request again.
- Keys last 24 hours and belong to your key (and user), so they never collide with anyone else's.

## Rate limits

Limits protect people, not the API's capacity:

- Sign-in: 10 wrong passwords in 15 minutes locks the account for the rest of that window.
- Emailed links (sign-in, password reset, confirmation): 5 an hour per address.

Over a limit you get `429` with `Retry-After`.

## Languages

The marketplace's languages are in `GET /v1/marketplace` → `locales` (BCP 47, the first is the default). Pass `locale` where an endpoint takes one (categories, catalogue terms); a user's `locale` decides the language of their emails.

## Versioning

Everything is under `/v1`. Within `v1`, changes only add things: new endpoints, new optional request fields, new response fields, new enum values. Write clients that ignore fields they don't know. Anything that would break a client gets a new version.

## The spec

The [OpenAPI 3.1 spec](https://dev.mongrl.com/openapi.json) ([YAML](https://dev.mongrl.com/openapi.yaml)) is generated from the API's code and published with it, so it always matches the API. It covers everything a storefront can call; the console's own endpoints aren't in it.
