# The five standard methods

There are five methods, and almost every one of the platform's endpoints is one of them on
some resource. Learn them once and the reference stops being a list of endpoints and
becomes a list of resources: five known shapes each.

```sh title="quickstart.sh"
curl_json "${auth[@]}" -X POST "$site/cms_blocks" \
  -d '{"title": "Store hours", "identifier": "store-hours", "is_active": true,
       "content": "<p>Open Monday to Friday, 9:00–18:00.</p>"}' >/dev/null

url_key="hello-api"
page=$(curl_json "${auth[@]}" -X POST "$site/cms_pages" \
  -d "$(jq -n --arg url_key "$url_key" \
    '{title: "Hello, API", url_key: $url_key, is_active: true,
      content: "<p>This page was created with three lines of curl.</p>"}')")

jq '{name, id, create_time, etag}' <<<"$page"
```

## 7. Standard methods

Five, and only five, have HTTP-shorthand status. Everything else is a custom
method with a name.

| Method | HTTP | Success | Body in | Body out |
|---|---|---|---|---|
| **List** (AIP-132) | `GET {collection}` | 200 | — | list response ([§8](/concepts/lists)) |
| **Get** (AIP-131) | `GET {name}` | 200 | — | the resource |
| **Create** (AIP-133) | `POST {collection}` | **201** + `Location` | the resource | the created resource |
| **Update** (AIP-134) | `PATCH {name}` | 200 | partial resource | the updated resource |
| **Delete** (AIP-135) | `DELETE {name}` | 204 (hard) / 200 (soft) | — | — / the resource |

Non-negotiable consequences of that table:

- **Mutations return the resource, not a receipt.** The client that
  just wrote something needs the server's view of it — including
  server-generated fields, `update_time` and the new `etag`.
- **`PATCH`, not `PUT`.** `PATCH`
  carries merge semantics: only the fields present in the body are written. That
  is what AIP-134 specifies.
- **Field-mask deletes.** Merge semantics alone cannot clear a field: omitting
  it means "leave it". A client that must clear one sends `?update_mask=` with
  the field listed and the field absent (or `null`) in the body. `update_mask`
  is a comma-separated list of top-level field paths. When `update_mask` is
  absent, every field present in the body is written and nothing else is
  touched.
- **Create id assignment.** Ids are server-assigned. A client that needs to
  choose one (imports, seeds, idempotent provisioning) passes `?id=` and the
  server either honours it or fails `ALREADY_EXISTS`.
- **Get on a missing resource is `NOT_FOUND`, always.** Including when the
  parent scope does not exist, and including when the caller may not see it —
  a tenant probing another tenant's website id gets `NOT_FOUND`, never
  `PERMISSION_DENIED`, because the alternative confirms existence.

Optional, per-resource, when the cost is justified:

- `?validate_only=true` on Create/Update — run every check, write nothing,
  return the resource that *would* result. The admin's form-level validation is
  the use case.
- `?allow_missing=true` on Update — upsert (AIP-134). Only where a caller
  genuinely cannot know whether the resource exists.

**The CORS preflight has to admit these methods.** `PATCH` is not a
CORS-"simple" method and neither `If-Match` ([§14](/concepts/concurrency)) nor `Authorization` is a
simple header, so a browser sends none of them unless
`Access-Control-Allow-Methods` / `-Headers` say it may — and when the preflight
refuses, the request never leaves the browser: no status, no server log,
nothing to read but a save that silently does nothing.

## 9. Singleton resources (AIP-156)

Some things exist at most once per parent and have no id of their own:
`websites/{w}/custom_domain`, `users/{u}/profile`.

- The name ends with the **singular** collection id.
- They support **Get** and **Update** only. No List, no Create, no Delete —
  creation is implicit in the first Update, and removal is a field or a custom
  method (`:disconnect`), never `DELETE` on the singleton.
- Get on a singleton that has never been written returns the resource with
  default values, not `NOT_FOUND`, unless the resource documents otherwise.
- A singleton carries **no `id`**. [§12](/concepts/json)'s `id` is the final segment of `name`,
  and a singleton's name has none — publishing its storage id under that field
  would break the one invariant `id` has. Everything else in [§12](/concepts/json) still applies:
  a stored singleton has `create_time`, `update_time` and an `etag`, and a
  never-written one omits the timestamps because there is nothing to date.

**Settings are not a singleton — they are a collection** keyed by their dotted
path:

```
GET    /tenant-api/v1/websites/{w}/settings/general.locale.default_language
PATCH  /tenant-api/v1/websites/{w}/settings/general.locale.default_language
GET    /tenant-api/v1/websites/{w}/settings?filter=path:"general.locale"
POST   /tenant-api/v1/websites/{w}/settings:batch_update
```

`:batch_update` is AIP-234: it takes a list of resources, and it is **atomic**
— all writes land or none do. Partial success is not a shape this API returns.

## 10. Custom methods (AIP-136)

When an operation is not one of the five, it is a custom method:

```
POST {name}:{verb}          ; acts on one resource
POST {collection}:{verb}    ; acts on the collection
```

- The separator is a colon, never a path segment. `…/menus/{m}/items/{i}:move`,
  not `…/items/{i}/move`.
- The verb is `snake_case` (D1) and is a **verb**: `:move`, `:verify`,
  `:reindex`, `:batch_update`, `:undelete`, `:authenticate`.
- Custom methods are `POST` unless they are side-effect-free reads that cannot
  be expressed as a List filter, which are `GET`.
- Request and response bodies are ordinary JSON objects. A custom method that
  mutates one resource SHOULD return that resource.
