openapi: 3.1.0 info: title: App Template API version: 0.1.0 description: | App template API — OpenAPI-first backend pairing with the client template and the @aetoskia/react-openapi admin panel renderer. Resources are driven by OpenAPI x- extension fields; auth is bearer JWT introspected against the auth-server. Item and Job are example domains — replace them with your own, keeping the x- conventions documented in the react-openapi MCP bundle (module react-openapi, object specExtensions). servers: - url: http://localhost:8000 security: - bearerAuth: [] components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT x-server-url: "{AUTH_SERVER}" x-introspect-path: "/introspect" x-login-path: "/login" x-register-path: "/register" x-logout-path: "/logout" x-me-path: "/me" schemas: Item: type: object x-resource: items x-primary-key: id x-display-format: "{name} × {quantity}" x-list-columns: [name, quantity] properties: id: type: integer readOnly: true x-order: 0 x-hidden: { form: true, list: true } x-label: "ID" name: type: string x-order: 1 x-label: "Name" x-description: "Item name" x-filterable: true x-sortable: true x-autocomplete: token quantity: type: integer default: 1 x-order: 2 x-label: "Quantity" x-description: "How many units" x-sortable: true required: [id, name] Job: type: object x-resource: jobs x-primary-key: id x-display-format: "{name} [{status}]" x-list-columns: [name, status, retry_count] properties: id: type: integer readOnly: true x-order: 0 x-hidden: { form: true, list: true } x-label: "ID" name: type: string x-order: 1 x-label: "Name" x-description: "Job name" x-filterable: true x-autocomplete: token status: type: string enum: [pending, processing, done, failed] x-order: 2 x-label: "Status" x-description: "Lifecycle status" x-filterable: true retry_count: type: integer readOnly: true x-order: 3 x-label: "Retries" error_message: type: string readOnly: true x-order: 4 x-label: "Last Error" payload: type: object x-order: 5 x-label: "Payload" x-description: "Arbitrary job payload" required: [id, name, status] ErrorBody: type: object properties: detail: type: string required: [detail] HTTPValidationError: type: object properties: detail: type: array items: $ref: '#/components/schemas/ValidationError' ValidationError: type: object properties: loc: type: array items: type: string msg: type: string type: type: string required: [loc, msg, type] responses: Unauthorized: description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorBody' Forbidden: description: Insufficient permissions content: application/json: schema: $ref: '#/components/schemas/ErrorBody' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ErrorBody' ValidationError: description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' InternalServerError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorBody' paths: /health: get: summary: Liveness probe (public, unauthenticated) operationId: health security: [] responses: '200': description: Service is alive content: application/json: schema: type: object properties: status: type: string service: type: string /items: get: summary: List items (paginated) operationId: list_items parameters: - in: query name: limit schema: {type: integer, default: 20} - in: query name: offset schema: {type: integer, default: 0} responses: '200': description: Paginated list of items content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Item' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create an item operationId: create_item requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Item' responses: '201': description: Item created content: application/json: schema: $ref: '#/components/schemas/Item' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /items/{id}: get: operationId: get_item parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single item content: application/json: schema: $ref: '#/components/schemas/Item' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' put: operationId: update_item parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Item' responses: '200': description: Item updated content: application/json: schema: $ref: '#/components/schemas/Item' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' delete: operationId: delete_item parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Item deleted '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /jobs: get: summary: List jobs (paginated) operationId: list_jobs parameters: - in: query name: limit schema: {type: integer, default: 20} - in: query name: offset schema: {type: integer, default: 0} responses: '200': description: Paginated list of jobs content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Job' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create a job operationId: create_job requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Job' responses: '201': description: Job created content: application/json: schema: $ref: '#/components/schemas/Job' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /jobs/{id}: get: operationId: get_job parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single job content: application/json: schema: $ref: '#/components/schemas/Job' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' put: operationId: update_job parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Job' responses: '200': description: Job updated content: application/json: schema: $ref: '#/components/schemas/Job' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' delete: operationId: delete_job parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Job deleted '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError'