API Definition
The common specification of the REST API exposed by the backend. See the per-resource pages in the left menu for individual endpoint definitions.
Method
REST methods are used.
HTTP Methods
| Method | Purpose |
|---|---|
| GET | Retrieval |
| POST | Creation, and state-changing operations (save, duplicate, archive, export, import) |
| PATCH | Partial update |
| PUT | Full replacement (bulk permission update, setting a favorite) |
| DELETE | Deletion |
Naming Conventions
Request URIs, path parameters, and JSON nodes use camelCase. DB column names use snake_case, but that is kept separate from the API representation.
Every endpoint lives under /api (except the health check at /, plus /openapi.json and /swagger).
Request and Response
Headers
Request headers
Content-Type:application/jsonx-user-email: email address of the logged-in user (required)x-user-name: display name of the logged-in user (required)x-user-image: avatar URL of the logged-in user (optional)x-api-key: import API only, when using API key authentication
Response headers
Content-Type:application/json
Response Body
Responses are JSON and always take this shape.
On failure:
| Field | Type | Notes |
|---|---|---|
| ok | boolean | true on success, false on failure |
| data | object | array | Success only. The resource or an array of resources |
| error | string | Failure only. The error message |
Authentication
The frontend passes the user information obtained from the next-auth session to the backend in HTTP headers.
| Method | Condition | Scope |
|---|---|---|
| User headers | x-user-email + x-user-name are both present |
All of /api/* |
| API key | SURVEY_IMPORT_API_KEY is set and x-api-key matches |
/api/imports/* only |
Automatic User Creation
If no user exists for the given email address, one is created when the request arrives.
- Read
x-user-email/x-user-name(401 if either is missing) - If the value contains
%, assume it wasencodeURIComponent-ed and try to decode it - Normalize the email address to lower case
- Treat the user as an administrator if the address is listed in
SURVEY_ADMIN_EMAILS(comma separated) - If the user exists, update them only if the display name or administrator flag changed
- If the user does not exist, create them
HTTP header character encoding
HTTP headers can only carry ISO-8859-1, so display names containing Japanese are encodeURIComponent-ed by the frontend.
Impersonation is possible
The current authentication trusts the headers, so forging them allows acting as any user. This is an open issue to address before a production rollout (Infrastructure).
Permissions
Operations on a survey are controlled by view / edit permissions. Administrators can access every survey regardless of permission records.
Error Handling
| Description | Status code | Status name |
|---|---|---|
| Malformed request body or parameters | 400 | Bad Request |
| Business rule violation (e.g. a question that cannot be a condition source) | 400 | Bad Request |
| Missing authentication headers, or a mismatched API key | 401 | Unauthorized |
| No permission to delete (surveys, past question data) | 403 | Forbidden |
| Target does not exist, or permission is missing | 404 | Not Found |
| Internal server error | 500 | Internal Server Error |
Missing permission collapses into 404
To hide whether a survey exists, missing permission generally returns 404. Only delete operations return 403, to make the intent explicit.
Endpoints
Surveys
| Method | URI | Overview |
|---|---|---|
| GET | /api/surveys |
List surveys |
| POST | /api/surveys |
Create a survey and generate an initial draft |
| GET | /api/surveys/{surveyId} |
Get survey detail |
| PATCH | /api/surveys/{surveyId} |
Update survey basics |
| DELETE | /api/surveys/{surveyId} |
Delete a survey (soft delete) |
| GET | /api/surveys/{surveyId}/theme |
Get the survey theme |
| PATCH | /api/surveys/{surveyId}/theme |
Update the survey theme |
| POST | /api/theme-assists |
Get theme suggestions |
| POST | /api/surveys/{surveyId}/save |
Save and create a new version |
| POST | /api/surveys/{surveyId}/versions |
Same (alias) |
| POST | /api/surveys/{surveyId}/duplicate |
Duplicate a survey |
| POST | /api/survey-copies |
Duplicate from a survey or a specific version |
| POST | /api/surveys/{surveyId}/questions/renumber |
Renumber the question codes |
| POST | /api/surveys/{surveyId}/archive |
Archive a survey |
| POST | /api/surveys/{surveyId}/export |
Export JSON and register into the question library |
| POST | /api/surveys/{surveyId}/exports |
Same (alias) |
| PUT | /api/surveys/{surveyId}/favorite |
Set or clear a favorite |
Sections
| Method | URI | Overview |
|---|---|---|
| POST | /api/surveys/{surveyId}/sections |
Add a section and generate an initial draft |
| PATCH | /api/sections/{sectionId} |
Update a section |
| DELETE | /api/sections/{sectionId} |
Delete a section |
| POST | /api/sections/{sectionId}/duplicate |
Duplicate a section |
| POST | /api/sections/import |
Import a section |
| POST | /api/sections/import-from-external |
Insert one section from external-origin entries |
Questions
| Method | URI | Overview |
|---|---|---|
| POST | /api/sections/{sectionId}/questions |
Add a question |
| PATCH | /api/questions/{questionId} |
Update a question |
| DELETE | /api/questions/{questionId} |
Delete a question |
| POST | /api/questions/{questionId}/duplicate |
Duplicate a question |
| POST | /api/questions/import |
Import a question |
Versions
| Method | URI | Overview |
|---|---|---|
| GET | /api/surveys/{surveyId}/versions |
List versions |
| POST | /api/surveys/{surveyId}/versions/{versionNo}/rollback |
Roll back from a version |
| POST | /api/surveys/{surveyId}/versions/{versionNo}/duplicate |
Duplicate from a version |
Permissions and Users
| Method | URI | Overview |
|---|---|---|
| GET | /api/surveys/{surveyId}/permissions |
List permissions |
| PUT | /api/surveys/{surveyId}/permissions |
Bulk-update permissions |
| GET | /api/users |
List users |
Question Library
| Method | URI | Overview |
|---|---|---|
| GET | /api/question-library |
List past question data |
| DELETE | /api/question-library/{itemId} |
Delete past question data |
Imports
| Method | URI | Overview |
|---|---|---|
| POST | /api/imports/creative-survey/bundle |
Import a Creative Survey bundle |
| GET | /api/imports/creative-survey/existing-ids |
List already-imported externalIds |
| GET | /api/imports/creative-survey/section-previews |
Section candidate preview (all) |
| GET | /api/imports/creative-survey/section-previews/{externalSurveyId} |
Section candidate preview (single) |
Others
| Method | URI | Overview |
|---|---|---|
| GET | / |
Health check |
| GET | /openapi.json |
OpenAPI document |
| GET | /swagger |
Swagger UI |
OpenAPI Document
The API is described with @hono/zod-openapi, and the OpenAPI document is generated from the Zod schemas.
flowchart LR
Zod[Zod schemas<br/>openapi-schemas.ts] --> Doc[openapi.json]
Doc --> Yaml[openapi.yaml<br/>normalized 3.1 โ 3.0]
Yaml --> Orval[orval]
Orval --> Client[lib/api/generated.ts<br/>react-query client]
After changing an API, propagate it in this order.
cd heineken-survey-design-backend && bun run export:openapi
cd ../heineken-survey-design-frontend && bun run generate:api
While running locally, the Swagger UI is available at http://localhost:8787/swagger.