kereru-forms/backend/openapi/openapi.yaml

1065 lines
29 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
- 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
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
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
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
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
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
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)
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)
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
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
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
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'
/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:
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:
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
Section:
type: object
required: [id, identifier]
properties:
id:
type: string
format: uuid
identifier:
type: string
description: Section identifier such as letter or number used to prefix question numbers
title:
type: string
description:
type: string
SectionInput:
type: object
required: [identifier]
properties:
identifier:
type: string
title:
type: string
description:
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
sectionId:
type: string
format: uuid
description: Identifier of the section this question belongs to
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'
sections:
type: array
items:
$ref: '#/components/schemas/Section'
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
sectionId:
type: string
format: uuid
description: Identifier of the section this question belongs to
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'
sections:
type: array
items:
$ref: '#/components/schemas/SectionInput'
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'
sections:
type: array
items:
$ref: '#/components/schemas/SectionInput'
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