Open API specification of server endpoints
This is the 1st draft of the Open API spec for our server endpoints.
openapi: 3.1.0
info:
title: See Something ABQ API
version: 0.3.0-milestone-3-draft
summary: OpenAPI specification aligned to the current Milestone 3 server controllers.
description: |
OpenAPI specification for the current authenticated server-side API.
Authentication model:
* All endpoints require a valid JWT bearer token.
* Manager-only endpoints are documented in each operation description.
* Manager authorization is enforced server-side from JWT role claims.
servers:
- url: /api
description: Application-relative base URL
security:
- bearerAuth: []
tags:
- name: Users
description: Endpoints for the currently authenticated user and manager user administration.
- name: Issue Reports
description: Endpoints for creating, reading, updating, deleting, and administrating issue reports.
- name: Issue Types
description: Endpoints for listing and managing issue type lookup values.
- name: Accepted States
description: Endpoints for managing accepted state lookup values.
- name: Report Images
description: Endpoints for adding and retrieving images associated with issue reports.
paths:
/users/me:
get:
tags: [Users]
summary: Get the current user's profile
operationId: getCurrentUser
description: Returns the profile for the authenticated user. The service may create the profile automatically if needed.
responses:
'200':
description: Current user profile retrieved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'401':
$ref: '#/components/responses/Unauthorized'
/users/me/display-name:
put:
tags: [Users]
summary: Update the current user's display name
operationId: updateCurrentUserDisplayName
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDisplayNameRequest'
responses:
'200':
description: User profile updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/users/me/email:
put:
tags: [Users]
summary: Update the current user's email address
operationId: updateCurrentUserEmail
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateEmailRequest'
responses:
'200':
description: User profile updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/users/me/avatar:
put:
tags: [Users]
summary: Update the current user's avatar URL
operationId: updateCurrentUserAvatar
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateAvatarRequest'
responses:
'200':
description: User profile updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/manager/users:
get:
tags: [Users]
summary: Get all user profiles
operationId: managerGetUsers
description: Manager role required. Returns all user profiles.
responses:
'200':
description: User profiles retrieved successfully.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UserProfile'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/manager/users/{externalId}:
parameters:
- $ref: '#/components/parameters/ExternalId'
get:
tags: [Users]
summary: Get a user profile by external ID
operationId: managerGetUserByExternalId
description: Manager role required.
responses:
'200':
description: User profile retrieved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/manager/users/{externalId}/manager-status:
parameters:
- $ref: '#/components/parameters/ExternalId'
patch:
tags: [Users]
summary: Update a user's manager status
operationId: managerUpdateUserManagerStatus
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ManagerStatusUpdateRequest'
responses:
'200':
description: User profile updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/manager/users/{externalId}/enabled:
parameters:
- $ref: '#/components/parameters/ExternalId'
patch:
tags: [Users]
summary: Update a user's enabled status
operationId: managerUpdateUserEnabled
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserEnabledUpdateRequest'
responses:
'200':
description: User profile updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/issue-reports/mine:
get:
tags: [Issue Reports]
summary: Get the authenticated user's issue reports
operationId: getMyIssueReports
parameters:
- name: sort
in: query
required: false
description: |
Sort selector for the current user's issue reports.
Supported field keys:
- `last_modified`
- `first_reported`
Supported formats:
- `last_modified`
- `last_modified,asc`
- `last_modified,desc`
- `first_reported,asc`
- `first_reported,desc`
- multiple clauses separated by semicolons, for example
`last_modified,desc;first_reported,desc`
Unknown field keys are ignored. If no valid clauses are provided,
results default to `last_modified` descending.
schema:
type: string
default: last_modified
example: last_modified,desc;first_reported,desc
responses:
'200':
description: Issue report summaries retrieved successfully.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/IssueReportSummary'
'401':
$ref: '#/components/responses/Unauthorized'
/issue-reports:
post:
tags: [Issue Reports]
summary: Create an issue report
operationId: createIssueReport
description: Creates an issue report for the authenticated user. The server derives ownership from the authenticated principal and sets the default accepted state internally.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReportWriteRequest'
responses:
'201':
description: Issue report created successfully.
headers:
Location:
description: Relative URL of the created issue report resource.
schema:
type: string
format: uri-reference
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReport'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/issue-reports/{externalKey}:
parameters:
- $ref: '#/components/parameters/ExternalKey'
get:
tags: [Issue Reports]
summary: Get an issue report by external key
operationId: getIssueReportByExternalKey
responses:
'200':
description: Issue report retrieved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReport'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Issue Reports]
summary: Update an issue report by external key
operationId: updateIssueReport
description: Updates user-editable fields on an existing issue report.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReportWriteRequest'
responses:
'200':
description: Issue report updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReport'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Issue Reports]
summary: Delete an issue report by external key
operationId: deleteIssueReport
responses:
'204':
description: Issue report deleted successfully.
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/manager/issue-reports:
get:
tags: [Issue Reports]
summary: Get paged issue reports for manager review
operationId: managerGetIssueReports
description: Manager role required. Returns issue reports in a paged response ordered by timeLastModified descending.
parameters:
- name: pageSize
in: query
required: false
schema:
type: integer
default: 20
minimum: 1
- name: pageNumber
in: query
required: false
schema:
type: integer
default: 0
minimum: 0
responses:
'200':
description: Paged issue reports retrieved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ManagerIssueReportPage'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/manager/issue-reports/{externalId}/status:
parameters:
- $ref: '#/components/parameters/ExternalId'
put:
tags: [Issue Reports]
summary: Update an issue report's accepted state
operationId: managerUpdateIssueReportStatus
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReportStatusUpdateRequest'
responses:
'200':
description: Issue report updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReport'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/manager/issue-reports/{externalId}/issue-types:
parameters:
- $ref: '#/components/parameters/ExternalId'
put:
tags: [Issue Reports]
summary: Replace an issue report's issue types
operationId: managerUpdateIssueReportTypes
description: Manager role required. Replaces the entire issue type set for the specified issue report.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReportTypesUpdateRequest'
responses:
'200':
description: Issue report updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueReport'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/issue-types:
get:
tags: [Issue Types]
summary: Get all issue types
operationId: getIssueTypes
responses:
'200':
description: Issue types retrieved successfully.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/IssueType'
'401':
$ref: '#/components/responses/Unauthorized'
/manager/issue-types:
post:
tags: [Issue Types]
summary: Create an issue type
operationId: managerCreateIssueType
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IssueType'
responses:
'200':
description: Issue type created successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueType'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
/manager/issue-types/{issueTypeTag}:
parameters:
- $ref: '#/components/parameters/IssueTypeTag'
patch:
tags: [Issue Types]
summary: Update an issue type description
operationId: managerUpdateIssueTypeDescription
description: Manager role required. The current implementation expects the request body to be a raw JSON string containing the new description.
requestBody:
required: true
content:
application/json:
schema:
type: string
description: New issue type description encoded as a JSON string value.
example: Illegal dumping or trash accumulation
responses:
'200':
description: Issue type updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/IssueType'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Issue Types]
summary: Delete an unused issue type
operationId: managerDeleteIssueType
description: Manager role required. Deletion succeeds only when no issue reports reference the issue type.
responses:
'204':
description: Issue type deleted successfully.
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
/manager/accepted-states:
get:
tags: [Accepted States]
summary: Get all accepted states
operationId: managerGetAcceptedStates
description: Manager role required.
responses:
'200':
description: Accepted states retrieved successfully.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AcceptedState'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
post:
tags: [Accepted States]
summary: Create an accepted state
operationId: managerCreateAcceptedState
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AcceptedState'
responses:
'200':
description: Accepted state created successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/AcceptedState'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
/manager/accepted-states/{statusTag}:
parameters:
- $ref: '#/components/parameters/StatusTag'
patch:
tags: [Accepted States]
summary: Update an accepted state description
operationId: managerUpdateAcceptedStateDescription
description: Manager role required.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AcceptedStateDescriptionUpdateRequest'
responses:
'200':
description: Accepted state updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/AcceptedState'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Accepted States]
summary: Delete an unused accepted state
operationId: managerDeleteAcceptedState
description: Manager role required. Deletion succeeds only when no issue reports reference the accepted state.
responses:
'204':
description: Accepted state deleted successfully.
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
/issue-reports/{externalKey}/images:
parameters:
- $ref: '#/components/parameters/ExternalKey'
post:
tags: [Report Images]
summary: Add an image to an issue report
operationId: addIssueReportImage
description: Adds an image to the specified issue report. The current implementation only allows the report owner to add images.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AddImageRequest'
responses:
'201':
description: Image added successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ReportImage'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/issue-reports/{externalKey}/images/{imageKey}:
parameters:
- $ref: '#/components/parameters/ExternalKey'
- $ref: '#/components/parameters/ImageKey'
get:
tags: [Report Images]
summary: Get an image associated with an issue report
operationId: getIssueReportImage
description: Returns a specific report image. The current implementation allows access to the report owner and managers.
responses:
'200':
description: Image retrieved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ReportImage'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT bearer token validated by the Spring resource server.
parameters:
ExternalId:
name: externalId
in: path
required: true
description: UUID external identifier.
schema:
type: string
format: uuid
ExternalKey:
name: externalKey
in: path
required: true
description: UUID external key.
schema:
type: string
format: uuid
ImageKey:
name: imageKey
in: path
required: true
description: UUID external key for a report image.
schema:
type: string
format: uuid
IssueTypeTag:
name: issueTypeTag
in: path
required: true
schema:
type: string
StatusTag:
name: statusTag
in: path
required: true
schema:
type: string
responses:
BadRequest:
description: The request body or parameters were invalid.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Authentication is required or the bearer token was invalid.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: The authenticated user does not have permission to perform this operation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: The requested resource was not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Conflict:
description: The operation could not be completed because of a uniqueness or in-use conflict.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
InternalServerError:
description: An unexpected server-side error occurred.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
ErrorResponse:
type: object
description: Generic error response schema used while exception handling is being standardized.
properties:
message:
type: string
description: Human-readable error message.
status:
type: integer
description: HTTP status code when available.
error:
type: string
description: HTTP reason phrase or error category when available.
timestamp:
type: string
format: date-time
description: Error timestamp when provided by the runtime.
path:
type: string
description: Request path when provided by the runtime.
UserProfile:
type: object
required: [externalId, displayName, email, isManager, timeCreated, userEnabled]
properties:
externalId:
type: string
format: uuid
displayName:
type: string
email:
type: string
avatar:
type: string
format: uri
nullable: true
isManager:
type: boolean
timeCreated:
type: string
format: date-time
userEnabled:
type: boolean
UpdateDisplayNameRequest:
type: object
required: [displayName]
properties:
displayName:
type: string
UpdateEmailRequest:
type: object
required: [email]
properties:
email:
type: string
UpdateAvatarRequest:
type: object
required: [avatar]
properties:
avatar:
type: string
format: uri
ManagerStatusUpdateRequest:
type: object
required: [manager]
properties:
manager:
type: boolean
UserEnabledUpdateRequest:
type: object
required: [enabled]
properties:
enabled:
type: boolean
ReportLocation:
type: object
properties:
latitude:
type: number
format: double
nullable: true
longitude:
type: number
format: double
nullable: true
streetCoordinate:
type: string
nullable: true
locationDescription:
type: string
nullable: true
AcceptedState:
type: object
required: [statusTag, statusTagDescription]
properties:
statusTag:
type: string
statusTagDescription:
type: string
IssueType:
type: object
required: [issueTypeTag, issueTypeDescription]
properties:
issueTypeTag:
type: string
issueTypeDescription:
type: string
ReportImage:
type: object
required: [externalKey, imageLocator, filename, mimeType, albumOrder]
properties:
externalKey:
type: string
format: uuid
imageLocator:
type: string
format: uri
filename:
type: string
mimeType:
type: string
albumOrder:
type: integer
AddImageRequest:
type: object
required: [imageLocator, filename, mimeType, albumOrder]
properties:
imageLocator:
type: string
format: uri
filename:
type: string
mimeType:
type: string
albumOrder:
type: integer
IssueReport:
type: object
required:
- externalId
- userProfile
- reportLocation
- acceptedState
- issueTypes
- timeFirstReported
- timeLastModified
- textDescription
- reportImages
properties:
externalId:
type: string
format: uuid
userProfile:
$ref: '#/components/schemas/UserProfile'
reportLocation:
$ref: '#/components/schemas/ReportLocation'
acceptedState:
$ref: '#/components/schemas/AcceptedState'
issueTypes:
type: array
items:
$ref: '#/components/schemas/IssueType'
timeFirstReported:
type: string
format: date-time
timeLastModified:
type: string
format: date-time
textDescription:
type: string
reportImages:
type: array
items:
$ref: '#/components/schemas/ReportImage'
IssueReportWriteRequest:
type: object
required: [reportLocation, textDescription]
properties:
reportLocation:
$ref: '#/components/schemas/ReportLocation'
issueTypes:
type: array
description: Present in the current entity shape, but manager update endpoints are the documented mechanism for replacing issue types.
items:
$ref: '#/components/schemas/IssueType'
textDescription:
type: string
reportImages:
type: array
description: Present in the current entity shape, but image creation is handled through the dedicated image endpoint.
items:
$ref: '#/components/schemas/ReportImage'
IssueReportSummary:
type: object
required:
- externalId
- description
- acceptedState
- timeFirstReported
- timeLastModified
properties:
externalId:
type: string
format: uuid
description:
type: string
acceptedState:
type: string
timeFirstReported:
type: string
format: date-time
timeLastModified:
type: string
format: date-time
IssueReportStatusUpdateRequest:
type: object
required: [statusTag]
properties:
statusTag:
type: string
IssueReportTypesUpdateRequest:
type: object
required: [issueTypeTags]
properties:
issueTypeTags:
type: array
items:
type: string
AcceptedStateDescriptionUpdateRequest:
type: object
required: [statusTagDescription]
properties:
statusTagDescription:
type: string
ManagerIssueReportResponse:
type: object
required:
- externalId
- userProfile
- reportLocation
- acceptedState
- issueTypes
- timeFirstReported
- timeLastModified
- textDescription
- reportImages
properties:
externalId:
type: string
format: uuid
userProfile:
$ref: '#/components/schemas/UserProfile'
reportLocation:
$ref: '#/components/schemas/ReportLocation'
acceptedState:
$ref: '#/components/schemas/AcceptedState'
issueTypes:
type: array
items:
$ref: '#/components/schemas/IssueType'
timeFirstReported:
type: string
format: date-time
timeLastModified:
type: string
format: date-time
textDescription:
type: string
reportImages:
type: array
items:
$ref: '#/components/schemas/ReportImage'
ManagerIssueReportPage:
type: object
description: Spring Data Page response for manager issue report listings.
properties:
content:
type: array
items:
$ref: '#/components/schemas/ManagerIssueReportResponse'
pageable:
type: object
additionalProperties: true
last:
type: boolean
totalElements:
type: integer
format: int64
totalPages:
type: integer
size:
type: integer
number:
type: integer
sort:
type: object
additionalProperties: true
first:
type: boolean
numberOfElements:
type: integer
empty:
type: boolean