# HTTP protocol (https://survey.dimah.dev/docs/protocol)



Route constants and payload schemas live in `@dimah-survey/core`.

| Audience | Default base path   | Operations                                          |
| -------- | ------------------- | --------------------------------------------------- |
| Fill     | `/api/survey`       | Published read and respondent response lifecycle    |
| Editor   | `/api/admin/survey` | Authoring, settings, publish, and response analysis |

The suffixes below are appended to those bases. Browser clients take flat
objects; `fill.api` and `editor.api` take `{ query }` or `{ body }`. An
operation from the wrong audience is not mounted and returns `NOT_FOUND`.

List endpoints default to 50 rows and cap at 100.

## Fill routes [#fill-routes]

| Method | Path                | Browser client                               | In-process API                  |
| ------ | ------------------- | -------------------------------------------- | ------------------------------- |
| `GET`  | `/survey/published` | `getPublishedSurvey(id)`                     | `getPublishedSurvey({ query })` |
| `POST` | `/response/start`   | `startResponse({ surveyId, respondentId? })` | `startResponse({ body })`       |
| `GET`  | `/response`         | `getResponse(id)`                            | `getResponse({ query })`        |
| `GET`  | `/responses`        | `listResponses(query)`                       | `listResponses({ query })`      |
| `POST` | `/response/partial` | `savePartial(input)`                         | `savePartial({ body })`         |
| `POST` | `/response/submit`  | `submitResponse(input)`                      | `submitResponse({ body })`      |
| `POST` | `/response/abandon` | `abandonResponse(input)`                     | `abandonResponse({ body })`     |
| `POST` | `/response/reopen`  | `reopenResponse(input)`                      | `reopenResponse({ body })`      |

On survey reads, `id` accepts a survey id or slug. `startResponse.surveyId`
accepts either as well. Every response mutation uses the response row id.

`getPublishedSurvey` returns `publishedJson` and `settings`. It omits
`draftJson`. It is available to both fill principals and does not stamp a
respondent.

The guard stamps `respondentId` for identified callers. A matching value in the
body is tolerated, but the browser cannot claim another owner. Anonymous
callers cannot list responses; identified callers cannot request
`include: "full"`.

## Editor routes [#editor-routes]

| Method | Path               | Browser client              | In-process API                 |
| ------ | ------------------ | --------------------------- | ------------------------------ |
| `GET`  | `/survey`          | `getSurvey(id)`             | `getSurvey({ query })`         |
| `POST` | `/survey`          | `saveSurvey(input)`         | `saveSurvey({ body })`         |
| `GET`  | `/surveys`         | `listSurveys(query)`        | `listSurveys({ query })`       |
| `POST` | `/survey/publish`  | `publishSurvey(input)`      | `publishSurvey({ body })`      |
| `POST` | `/survey/archive`  | `archiveSurvey(input)`      | `archiveSurvey({ body })`      |
| `POST` | `/survey/settings` | `saveSurveySettings(input)` | `saveSurveySettings({ body })` |
| `POST` | `/survey/resume`   | `resumeSurvey(input)`       | `resumeSurvey({ body })`       |
| `GET`  | `/response`        | `getResponse(id)`           | `getResponse({ query })`       |
| `GET`  | `/responses`       | `listResponses(query)`      | `listResponses({ query })`     |

`saveSurvey` creates the survey when the id is new. `slug` defaults to `id`.
Pass `expectedUpdatedAt` when updating. A create that includes the token fails
with `STALE_UPDATE`.

`listSurveys` returns `{ surveys, limit, offset, nextOffset }`.
`listResponses` returns `{ responses, limit, offset, nextOffset, total }`.

## List queries [#list-queries]

`listResponses` accepts:

```ts
import type { ListResponsesQuery } from "@dimah-survey/core";
```

<AutoTypeTable path="packages/core/src/types.ts" name="ListResponsesQuery" />

`"summary"` omits `definition` and `data`. `"full"` is the editor analytics
read. Rows sort by `updatedAt` descending.

`listSurveys` accepts:

```ts
import type { ListSurveysQuery } from "@dimah-survey/core";
```

<AutoTypeTable path="packages/core/src/types.ts" name="ListSurveysQuery" />

## Compare-and-swap inputs [#compare-and-swap-inputs]

`expectedUpdatedAt` is optional on draft, publish, archive, settings, resume,
partial save, submit, abandon, and reopen. When it is set, the stored
`updatedAt` must match or the write fails with `STALE_UPDATE`. The React
bindings always send the token from their last successful read or write.

The exported Zod schemas are the authoritative payload definitions. See
[Errors](https://survey.dimah.dev/docs/errors.md) for the stable failure codes.
