---
name: rest-endpoint-conventions
source: https://app.decimal.ai/s/rest-endpoint-conventions@1/SKILL.md
source_sha256: fe1119b7629d
---

# REST Endpoint Conventions

Reviews or designs an HTTP API surface so the paths and response codes match what other REST clients expect. A capable model knows REST but does not apply it by default: it will accept a verb in the path, return `200` where the protocol wants `201`/`204`, reach for `400` on a conflict, and nest resources three deep. This skill pins the conventions so the surface is predictable and self-consistent.

Out of scope (a sibling owns each): how to paginate or filter a list, GraphQL schema shape, and query performance.

## When to Activate

- Review these endpoint paths / status codes before we ship
- What should this route be called?
- What should this endpoint return on success?
- Sign off on this new API surface
- A teammate proposed a route and I'm not sure it's idiomatic

## Path naming

1. **Resources are nouns, never verbs.** The HTTP method already carries the verb. `POST /orders` creates; the path is the *thing*, not the action. Reject `POST /createOrder`, `GET /getOrder`, `POST /order/delete`.
2. **Plural collections.** A collection is `/orders`; a member is `/orders/{id}`. Not `/order/{id}`.
3. **kebab-case segments.** Multi-word segments use hyphens, lowercase: `/user-profiles`, not `/userProfiles` or `/user_profiles`.
4. **Nesting at most two levels deep.** Express one containment relationship, e.g. `/orders/{id}/line-items`. If a child has its own globally unique id, address it at the top level (`/line-items/{id}`) instead of `/orgs/{id}/orders/{id}/line-items/{id}`. Deep hierarchies are brittle and hard to route.
5. **The method must match the effect.** A read is `GET` and must not change state; a state change is `POST`/`PUT`/`PATCH`/`DELETE`. Never mutate on `GET` (`GET /orders/{id}/cancel` is wrong — use `POST /orders/{id}/cancellation` or `DELETE`).
6. **A non-CRUD action is a sub-resource, scoped under its owner.** To publish a document, `POST /documents/{id}/publication` (or a state transition on the resource) — not a top-level verb route like `POST /publishDocument`.

## Status codes

Return the specific code for the outcome, not a generic `200`/`400`.

| Situation | Code | Notes |
|---|---|---|
| Created a new resource | `201 Created` | Return the resource; set a `Location` header to its URL. Not `200`. |
| Succeeded with no body to return (delete, some updates) | `204 No Content` | Empty body. Not `200` with `{}`. |
| Read / updated an existing resource, returning it | `200 OK` | `PUT`/`PATCH` that updates returns `200`, not `201` (`201` only when it creates). |
| Request accepted for async processing, not yet done | `202 Accepted` | Use when work is queued, not completed inline. |
| Body is well-formed but fails validation rules | `422 Unprocessable Entity` | Reserve `400` for malformed/unparseable requests. |
| Violates a uniqueness or state constraint (duplicate, already-taken) | `409 Conflict` | A duplicate email is a conflict, not a `400`. |
| Caller is authenticated but not allowed, and the resource's existence is not secret | `403 Forbidden` | The resource exists; the action is denied. |
| Resource does not exist, or you must not reveal that it exists to this caller | `404 Not Found` | Prefer `404` over `403` when confirming existence itself would leak (e.g. another tenant's record). |
| Method not supported on this path | `405 Method Not Allowed` | Include an `Allow` header. |

## Error body

Every error response uses **one consistent JSON shape** across the whole API — do not let different endpoints invent their own. A workable default:

```json
{ "error": { "code": "order_not_found", "message": "No order with id 42", "details": [] } }
```

A stable machine-readable `code`, a human `message`, and optional structured `details` (e.g. per-field validation errors). Pick a shape once; apply it everywhere. Success and error responses should be distinguishable without guessing.

## Output

When reviewing, report each deviation as: the path or response, what convention it breaks, and the corrected form. Confirm the parts that are already correct so the reader knows they were checked.

| Endpoint | Issue | Fix |
|---|---|---|
| `POST /createOrder` | verb in path | `POST /orders` |
| delete returns `200 {}` | wrong success code | `204 No Content` |
| duplicate email → `400` | wrong error code | `409 Conflict` |
