kereru-forms/backend/openapi/openapi.yaml

1021 lines
28 KiB
YAML
Raw Normal View History

openapi: 3.0.3
info:
title: Kererū Forms API
version: 1.0.0
description: |
API for **Kererū forms**, a simple survey/form capture system.
Forms are versioned and can be inactivated.
A form contains an ordered set of
questions whose answers are one of: true/false, numeric, a drop-down selection,
date or free-form text.
Responses are identified by a user identifier captured at the
start of the form. A form may optionally require a single-use access code.
contact:
name: Kererū Forms
email: support@kereru.forms
license:
name: MIT
servers:
- url: http://localhost:8080
description: Local development
- url: http://localhost:8080/api/v1
description: Local development (v1 base)
tags:
- name: system
description: Health and meta endpoints
- name: forms
description: Form definition and management
- name: submissions
description: Form submissions and management of captured data
- name: access-codes
description: Single-use access codes
2026-10-23 19:36:02 +00:00
- name: auth
description: Password fallback and OIDC-backed bearer authentication
paths:
/health:
get:
tags: [system]
operationId: getHealth
summary: Service health
responses:
'200':
description: Service is healthy
content:
application/json:
schema:
$ref: '#/components/schemas/Health'
/forms:
get:
tags: [forms]
operationId: listForms
summary: List forms
description: Returns form summaries. Use `includeInactive` to include inactivated forms (management view).
parameters:
- name: includeInactive
in: query
required: false
schema:
type: boolean
default: false
- name: activeOnly
in: query
required: false
deprecated: true
schema:
type: boolean
responses:
'200':
description: A list of forms
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FormSummary'
'500':
$ref: '#/components/responses/Error'
post:
tags: [forms]
operationId: createForm
summary: Create a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateFormRequest'
responses:
'201':
description: Form created
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'400':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}:
parameters:
- $ref: '#/components/parameters/FormId'
get:
tags: [forms]
operationId: getForm
summary: Get a form by id
responses:
'200':
description: The form
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
put:
tags: [forms]
operationId: updateForm
summary: Update a form definition
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateFormRequest'
responses:
'200':
description: Updated form
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'400':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
delete:
tags: [forms]
operationId: deleteForm
summary: Delete a form definition
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'204':
description: Deleted
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/inactivate:
parameters:
- $ref: '#/components/parameters/FormId'
post:
tags: [forms]
operationId: inactivateForm
summary: Inactivate a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
description: Inactivated forms remain visible in management but cannot accept new submissions.
responses:
'200':
description: Inactivated form
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/activate:
parameters:
- $ref: '#/components/parameters/FormId'
post:
tags: [forms]
operationId: activateForm
summary: Activate a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'200':
description: Activated form
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/versions:
parameters:
- $ref: '#/components/parameters/FormId'
post:
tags: [forms]
operationId: createFormVersion
summary: Create a new version of a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
description: Clones the form and its questions into a new version with the version number incremented. The new version is active; the previous version is inactivated.
responses:
'201':
description: New form version
content:
application/json:
schema:
$ref: '#/components/schemas/Form'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/pdf:
parameters:
- $ref: '#/components/parameters/FormId'
get:
tags: [forms]
operationId: getFormPdf
summary: Download a PDF rendering of the form definition
responses:
'200':
description: PDF document generated from the form definition
content:
application/pdf:
schema:
type: string
format: binary
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/submissions:
parameters:
- $ref: '#/components/parameters/FormId'
get:
tags: [submissions]
operationId: listFormSubmissions
summary: List submissions for a form (management)
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'200':
description: Submissions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SubmissionSummary'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
post:
tags: [submissions]
operationId: submitForm
summary: Submit an answer set for a form
description: |
Captures a completed form. The `userId` (captured at the start of the form)
identifies the respondent. If the form requires an access code, a valid,
unused `accessCode` must be supplied and is consumed on success.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SubmitFormRequest'
responses:
'201':
description: Submission accepted
content:
application/json:
schema:
$ref: '#/components/schemas/Submission'
'400':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'409':
$ref: '#/components/responses/Error'
'410':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/submissions/{submissionId}:
parameters:
- name: submissionId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [submissions]
operationId: getSubmission
summary: Get a submission by id (management)
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'200':
description: Submission
content:
application/json:
schema:
$ref: '#/components/schemas/Submission'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/submissions/{submissionId}/offline-pdf:
parameters:
- name: submissionId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [submissions]
operationId: getSubmissionOfflinePdf
summary: Download the offline PDF uploaded with a submission
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'200':
description: The uploaded PDF
content:
application/pdf:
schema:
type: string
format: binary
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
/forms/{formId}/access-codes:
parameters:
- $ref: '#/components/parameters/FormId'
get:
tags: [access-codes]
operationId: listAccessCodes
summary: List access codes for a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
responses:
'200':
description: Access codes
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AccessCode'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
post:
tags: [access-codes]
operationId: createAccessCodes
summary: Create one or more access codes for a form
2026-10-23 19:36:02 +00:00
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateAccessCodesRequest'
responses:
'201':
description: Created access codes
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AccessCode'
'400':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'500':
$ref: '#/components/responses/Error'
2026-10-23 19:36:02 +00:00
/auth/config:
get:
tags: [auth]
operationId: getAuthConfig
summary: Get browser authentication configuration
description: |
When `mode` is `oidc`, the SPA performs an authorization-code flow with
PKCE against the configured provider and then presents the returned ID
token as a bearer token to this API. When `mode` is `password`, the SPA
uses `/auth/login`.
responses:
'200':
description: Authentication configuration
content:
application/json:
schema:
$ref: '#/components/schemas/AuthConfig'
/auth/login:
post:
tags: [auth]
operationId: login
summary: Log in with the local password fallback
description: Only available when no OIDC issuer is configured.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: Signed local JWT
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
/auth/me:
get:
tags: [auth]
operationId: getCurrentUser
summary: Get the current authenticated user
security:
- bearerAuth: []
responses:
'200':
description: Current authentication state
content:
application/json:
schema:
$ref: '#/components/schemas/AuthMeResponse'
'401':
$ref: '#/components/responses/Error'
/auth/logout:
post:
tags: [auth]
operationId: logout
summary: Log out
description: Stateless tokens are discarded by the client; this endpoint exists for symmetry.
security:
- bearerAuth: []
responses:
'204':
description: Logged out
components:
2026-10-23 19:36:02 +00:00
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
Local password-mode JWT, or an OIDC ID token obtained by the SPA using
the authorization-code flow with PKCE.
parameters:
FormId:
name: formId
in: path
required: true
schema:
type: string
format: uuid
responses:
Error:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
2026-10-23 19:36:02 +00:00
AuthConfig:
type: object
required: [mode, requireSubmissionAuth]
properties:
mode:
type: string
enum: [password, oidc]
requireSubmissionAuth:
type: boolean
oidc:
$ref: '#/components/schemas/OIDCClientConfig'
OIDCClientConfig:
type: object
required: [issuerUrl, authorizationEndpoint, tokenEndpoint, clientId, scopes]
properties:
issuerUrl:
type: string
format: uri
authorizationEndpoint:
type: string
format: uri
tokenEndpoint:
type: string
format: uri
clientId:
type: string
scopes:
type: array
items:
type: string
LoginRequest:
type: object
required: [password]
properties:
password:
type: string
format: password
LoginResponse:
type: object
required: [token, user]
properties:
token:
type: string
user:
$ref: '#/components/schemas/AuthUser'
AuthUser:
type: object
required: [id, roles, canAdmin, canView]
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
roles:
type: array
items:
type: string
canAdmin:
type: boolean
canView:
type: boolean
AuthMeResponse:
type: object
required: [authenticated]
properties:
authenticated:
type: boolean
user:
$ref: '#/components/schemas/AuthUser'
Health:
type: object
required: [status, version]
properties:
status:
type: string
example: ok
version:
type: string
example: 1.0.0
uptimeSeconds:
type: integer
format: int64
QuestionType:
type: string
description: |
* `boolean` – true/false
* `number` – numeric answer
* `select` – one option from a configured list
* `text` – free-form text (single or multiline)
* `date` - calendar date in `YYYY-MM-DD` form
enum: [boolean, number, select, text, date]
QuestionOption:
type: object
required: [value, label]
properties:
value:
type: string
label:
type: string
Question:
type: object
required: [id, position, key, label, type, required]
properties:
id:
type: string
format: uuid
position:
type: integer
key:
type: string
description: Stable machine key for this question within the form
label:
type: string
description: The question text (may be multiline, no formatting directives)
description:
type: string
type:
$ref: '#/components/schemas/QuestionType'
required:
type: boolean
options:
type: array
description: Present for `select` questions
items:
$ref: '#/components/schemas/QuestionOption'
minValue:
type: number
description: Minimum allowed value for `number` questions
maxValue:
type: number
description: Maximum allowed value for `number` questions
placeholder:
type: string
multiline:
type: boolean
description: Whether a `text` answer may span multiple lines
FormSummary:
type: object
required: [id, formGroupId, version, title, isActive, questionCount, createdAt, updatedAt]
properties:
id:
type: string
format: uuid
formGroupId:
type: string
format: uuid
description: Groups all versions of the same logical form
version:
type: integer
title:
type: string
description:
type: string
logoUrl:
type: string
pdfUrl:
type: string
description: Optional absolute URL of a prebuilt PDF form linked to this definition
externalReferenceRequired:
type: boolean
description: Whether a reference identifier is required before the form can be submitted
externalReferenceLabel:
type: string
description: Label shown for the external reference, for example Invoice number
isActive:
type: boolean
questionCount:
type: integer
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
Form:
type: object
required: [id, formGroupId, version, title, isActive, requiresAccessCode, questions, createdAt, updatedAt]
properties:
id:
type: string
format: uuid
formGroupId:
type: string
format: uuid
version:
type: integer
title:
type: string
description:
type: string
introduction:
type: string
description: Introduction section explaining purpose, background and support details
isActive:
type: boolean
requiresAccessCode:
type: boolean
signatureRequired:
type: boolean
description: Whether a digital signature is captured at the end of the form
logoUrl:
type: string
pdfUrl:
type: string
description: Optional absolute URL of a prebuilt PDF form linked to this definition
externalReferenceRequired:
type: boolean
description: Whether a reference identifier is required before the form can be submitted
externalReferenceLabel:
type: string
description: Label shown for the external reference, for example Invoice number
companyName:
type: string
contactEmail:
type: string
contactPhone:
type: string
contactUrl:
type: string
questions:
type: array
items:
$ref: '#/components/schemas/Question'
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
QuestionInput:
type: object
required: [key, label, type, required]
properties:
key:
type: string
label:
type: string
description:
type: string
type:
$ref: '#/components/schemas/QuestionType'
required:
type: boolean
options:
type: array
items:
$ref: '#/components/schemas/QuestionOption'
minValue:
type: number
maxValue:
type: number
placeholder:
type: string
multiline:
type: boolean
CreateFormRequest:
type: object
required: [title, questions]
properties:
title:
type: string
description:
type: string
introduction:
type: string
requiresAccessCode:
type: boolean
signatureRequired:
type: boolean
logoUrl:
type: string
pdfUrl:
type: string
externalReferenceRequired:
type: boolean
externalReferenceLabel:
type: string
companyName:
type: string
contactEmail:
type: string
contactPhone:
type: string
contactUrl:
type: string
questions:
type: array
items:
$ref: '#/components/schemas/QuestionInput'
UpdateFormRequest:
type: object
required: [title, questions]
properties:
title:
type: string
description:
type: string
introduction:
type: string
requiresAccessCode:
type: boolean
signatureRequired:
type: boolean
logoUrl:
type: string
pdfUrl:
type: string
externalReferenceRequired:
type: boolean
externalReferenceLabel:
type: string
companyName:
type: string
contactEmail:
type: string
contactPhone:
type: string
contactUrl:
type: string
questions:
type: array
items:
$ref: '#/components/schemas/QuestionInput'
UserIdType:
type: string
enum: [email, username, phone, foreignkey guid]
Answer:
type: object
required: [questionId, value]
properties:
questionId:
type: string
format: uuid
value:
description: Answer value. String, number, boolean or list of strings depending on the question type.
SubmitFormRequest:
type: object
required: [userId, userIdType, answers]
properties:
userId:
type: string
description: Respondent identifier captured at the start (email, username, phone, foreignkey or GUID)
userIdType:
$ref: '#/components/schemas/UserIdType'
accessCode:
type: string
description: Required when the form has `requiresAccessCode = true`
externalReference:
type: string
description: |-
External reference identifier used to link this response to a record in
another system, for example an invoice, account or policy number.
answers:
type: array
items:
$ref: '#/components/schemas/Answer'
signature:
$ref: '#/components/schemas/SignatureInput'
offlinePdf:
type: string
description: |
Base64 data URL of a PDF completed offline (for example
`data:application/pdf;base64,...`). Maximum decoded size is 10 MB.
offlinePdfName:
type: string
description: Original filename of the uploaded offline PDF
tracking:
$ref: '#/components/schemas/ClientTracking'
ClientTracking:
type: object
description: Client supplied tracking details. IP and user agent are captured server-side.
properties:
locale:
type: string
timezone:
type: string
place:
type: string
referer:
type: string
SignatureType:
type: string
description: |
* `drawn` - signature drawn in the browser
* `image` - uploaded image containing a signature
enum: [drawn, image]
SignatureInput:
type: object
required: [type, dataUrl, name]
properties:
type:
$ref: '#/components/schemas/SignatureType'
dataUrl:
type: string
description: PNG or JPEG image data URL (maximum decoded size 2 MB)
name:
type: string
description: Name of the person signing
title:
type: string
description: Optional title of the person signing
Signature:
type: object
required: [type, dataUrl, name, signedAt]
properties:
type:
$ref: '#/components/schemas/SignatureType'
dataUrl:
type: string
name:
type: string
title:
type: string
signedAt:
type: string
format: date-time
AnswerRecord:
type: object
required: [questionId, questionKey, value]
properties:
questionId:
type: string
format: uuid
questionKey:
type: string
value:
description: The captured value
Tracking:
type: object
properties:
ipAddress:
type: string
userAgent:
type: string
locale:
type: string
timezone:
type: string
place:
type: string
referer:
type: string
Submission:
type: object
required: [id, formId, formVersion, userId, userIdType, answers, submittedAt]
properties:
id:
type: string
format: uuid
formId:
type: string
format: uuid
formVersion:
type: integer
userId:
type: string
userIdType:
$ref: '#/components/schemas/UserIdType'
externalReference:
type: string
answers:
type: array
items:
$ref: '#/components/schemas/AnswerRecord'
signature:
$ref: '#/components/schemas/Signature'
offlinePdfName:
type: string
description: Present when the respondent uploaded a completed offline PDF
submittedAt:
type: string
format: date-time
tracking:
$ref: '#/components/schemas/Tracking'
SubmissionSummary:
type: object
required: [id, formId, formVersion, userId, userIdType, submittedAt]
properties:
id:
type: string
format: uuid
formId:
type: string
format: uuid
formVersion:
type: integer
userId:
type: string
userIdType:
$ref: '#/components/schemas/UserIdType'
externalReference:
type: string
submittedAt:
type: string
format: date-time
AccessCode:
type: object
required: [id, formId, code, used, createdAt]
properties:
id:
type: string
format: uuid
formId:
type: string
format: uuid
code:
type: string
used:
type: boolean
usedAt:
type: string
format: date-time
createdAt:
type: string
format: date-time
CreateAccessCodesRequest:
type: object
required: [count]
properties:
count:
type: integer
minimum: 1
maximum: 100
prefix:
type: string
Error:
type: object
required: [error]
properties:
error:
type: string
code:
type: string