openapi: 3.1.0 info: title: Veterinary Clinic API version: 1.0.0 description: | Veterinary clinic management API demonstrating OpenAPI x- extension fields for the react-openapi admin panel renderer. servers: - url: http://localhost:8000 components: schemas: Metadata: type: object x-display-format: "created:{createdOn}|updated:{updatedOn}" properties: createdOn: type: string format: date-time updatedOn: type: string format: date-time Procedure: type: object x-display-format: "{name} — ${cost}" properties: name: type: string x-order: 1 x-label: "Procedure Name" description: type: string x-order: 2 x-label: "Description" cost: type: number format: float x-order: 3 x-label: "Cost" notes: $ref: '#/components/schemas/ProcedureNotes' x-order: 4 x-label: "Notes" ProcedureNotes: type: object x-display-format: "{summary}" properties: summary: type: string x-order: 1 x-label: "Summary" details: type: string x-order: 2 x-label: "Details" Parent: type: object x-resource: parents x-primary-key: id x-display-format: "{name}" x-list-columns: [name, email, phone] 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: "Parent's full name" x-filterable: true x-sortable: true email: type: string format: email x-order: 2 x-label: "Email" x-description: "Email address" x-filterable: true phone: type: string x-order: 3 x-label: "Phone" x-description: "Contact phone number" x-filterable: true metadata: $ref: '#/components/schemas/Metadata' x-order: 4 x-label: "Metadata" required: [id, name, email] Vet: type: object x-resource: vets x-primary-key: id x-display-format: "Dr. {name}" x-list-columns: [name, specialty, email, phone] 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: "Veterinarian's full name" x-filterable: true x-sortable: true specialty: type: string x-order: 2 x-label: "Specialty" x-description: "Area of specialization" x-filterable: true email: type: string format: email x-order: 3 x-label: "Email" x-description: "Email address" x-filterable: true phone: type: string x-order: 4 x-label: "Phone" x-description: "Contact phone number" metadata: $ref: '#/components/schemas/Metadata' x-order: 4 x-label: "Metadata" required: [id, name] Treatment: type: object x-resource: treatments x-primary-key: id x-display-format: "{label}" x-list-columns: [label, description] properties: id: type: integer readOnly: true x-order: 0 x-hidden: { form: true, list: true } x-label: "ID" label: type: string x-order: 1 x-label: "Treatment" x-description: "Name of the treatment" x-filterable: true x-sortable: true description: type: string x-order: 2 x-label: "Description" x-description: "Detailed description of the treatment" metadata: $ref: '#/components/schemas/Metadata' x-order: 4 x-label: "Metadata" required: [id, label] Pet: type: object x-resource: pets x-primary-key: id x-display-format: "{name} – #{id}" x-list-columns: [name, species, age, weight, birthDate, parents] 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: "Pet Name" x-description: "Name of the pet" x-filterable: true x-sortable: true species: type: string enum: [dog, cat, bird] x-order: 2 x-label: "Species" x-description: "Type of animal" x-filterable: true age: type: integer x-order: 3 x-label: "Age" x-description: "Age in years" x-filterable: true x-sortable: true weight: type: number format: float x-order: 4 x-label: "Weight" x-description: "Weight in kilograms" birthDate: type: string format: date x-order: 5 x-label: "Date of Birth" x-description: "Pet's birth date" x-filterable: true photo: type: string format: binary x-ui-type: image x-upload-url: /pets/{id}/photo x-order: 6 x-label: "Photo" x-description: "Upload a photo of the pet" parents: type: array items: $ref: '#/components/schemas/Parent' x-fk: resource: parents x-order: 7 x-label: "Parents" x-description: "Pet's owners" x-filterable: true metadata: $ref: '#/components/schemas/Metadata' x-order: 4 x-label: "Metadata" required: [id, name, parents] Appointment: type: object x-resource: appointments x-primary-key: id x-display-format: "Appt #{id} – {date}" x-list-columns: [date, pet, vet, treatment, notes] properties: id: type: integer readOnly: true x-order: 0 x-hidden: { form: true, list: true } x-label: "ID" date: type: string format: date-time x-order: 1 x-label: "Date & Time" x-description: "Appointment date and time" x-filterable: true x-sortable: true notes: type: string x-order: 2 x-label: "Notes" x-description: "Any additional notes" procedures: type: array items: $ref: '#/components/schemas/Procedure' x-order: 3 x-label: "Procedures" x-description: "List of procedures performed during this appointment" pet: $ref: '#/components/schemas/Pet' x-fk: resource: pets x-order: 3 x-label: "Pet" x-description: "Select a pet" x-filterable: true vet: $ref: '#/components/schemas/Vet' x-fk: resource: vets prefetch: true x-order: 4 x-label: "Veterinarian" x-description: "Select a veterinarian" x-filterable: true treatment: $ref: '#/components/schemas/Treatment' x-fk: resource: treatments prefetch: true x-order: 5 x-label: "Treatment" x-description: "Select a treatment" x-filterable: true metadata: $ref: '#/components/schemas/Metadata' x-order: 4 x-label: "Metadata" required: [id, date, pet, vet, treatment] 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: /parents: get: summary: List parents (paginated) operationId: list_parents 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 parents content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Parent' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create a parent operationId: create_parent requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Parent' responses: '201': description: Parent created content: application/json: schema: $ref: '#/components/schemas/Parent' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /parents/{id}: get: operationId: get_parent parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single parent content: application/json: schema: $ref: '#/components/schemas/Parent' '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_parent parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Parent' responses: '200': description: Parent updated content: application/json: schema: $ref: '#/components/schemas/Parent' '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_parent parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Parent 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' /vets: get: summary: List vets (paginated) operationId: list_vets 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 vets content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Vet' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create a vet operationId: create_vet requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Vet' responses: '201': description: Vet created content: application/json: schema: $ref: '#/components/schemas/Vet' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /vets/{id}: get: operationId: get_vet parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single vet content: application/json: schema: $ref: '#/components/schemas/Vet' '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_vet parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Vet' responses: '200': description: Vet updated content: application/json: schema: $ref: '#/components/schemas/Vet' '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_vet parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Vet 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' /treatments: get: summary: List treatments (catalogue) operationId: list_treatments responses: '200': description: List of treatments content: application/json: schema: type: array items: $ref: '#/components/schemas/Treatment' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Add a treatment (admin only) operationId: create_treatment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Treatment' responses: '201': description: Treatment added content: application/json: schema: $ref: '#/components/schemas/Treatment' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /treatments/{id}: get: operationId: get_treatment parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single treatment content: application/json: schema: $ref: '#/components/schemas/Treatment' '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_treatment parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Treatment' responses: '200': description: Treatment updated content: application/json: schema: $ref: '#/components/schemas/Treatment' '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_treatment parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Treatment 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' /pets: get: summary: List pets (paginated) operationId: list_pets 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 pets content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Pet' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create a pet operationId: create_pet requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Pet' responses: '201': description: Pet created content: application/json: schema: $ref: '#/components/schemas/Pet' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /pets/{id}: get: operationId: get_pet parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single pet content: application/json: schema: $ref: '#/components/schemas/Pet' '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_pet parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Pet' responses: '200': description: Pet updated content: application/json: schema: $ref: '#/components/schemas/Pet' '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_pet parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Pet 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' post: summary: Upload pet photo operationId: upload_pet_photo parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: '200': description: Photo uploaded '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /appointments: get: summary: List appointments (paginated, filterable) operationId: list_appointments parameters: - in: query name: limit schema: {type: integer, default: 20} - in: query name: offset schema: {type: integer, default: 0} - in: query name: date schema: {type: string, format: date} - in: query name: vet schema: {type: integer} - in: query name: pet schema: {type: integer} responses: '200': description: Paginated list of appointments content: application/json: schema: type: object properties: total: type: integer items: type: array items: $ref: '#/components/schemas/Appointment' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' post: summary: Create an appointment operationId: create_appointment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Appointment' responses: '201': description: Appointment created content: application/json: schema: $ref: '#/components/schemas/Appointment' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' '500': $ref: '#/components/responses/InternalServerError' /appointments/{id}: get: operationId: get_appointment parameters: - name: id in: path required: true schema: {type: integer} responses: '200': description: Single appointment content: application/json: schema: $ref: '#/components/schemas/Appointment' '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_appointment parameters: - name: id in: path required: true schema: {type: integer} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Appointment' responses: '200': description: Appointment updated content: application/json: schema: $ref: '#/components/schemas/Appointment' '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_appointment parameters: - name: id in: path required: true schema: {type: integer} responses: '204': description: Appointment 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'