83ab8c4cf9
Full OpenAPI 3.0.3 spec with all 30+ endpoints, schemas, and query params. Swagger UI served from unpkg CDN at /api/docs, raw spec at /api/openapi.yaml.
1991 lines
51 KiB
YAML
1991 lines
51 KiB
YAML
openapi: "3.0.3"
|
|
info:
|
|
title: HerbAPI
|
|
description: |
|
|
Plant reference database for permaculture and agroforestry planning.
|
|
Covers botanical families, species, cultivars, suppliers, companion planting
|
|
relationships, and images.
|
|
|
|
**Authentication:** Write operations require an authenticated session via
|
|
OIDC (Authentik). Read endpoints are public.
|
|
version: "1.0.0"
|
|
contact:
|
|
name: Sub-Net e.U.
|
|
url: https://sub-net.at
|
|
email: florian.berthold@sub-net.at
|
|
|
|
servers:
|
|
- url: /
|
|
description: Current instance
|
|
|
|
tags:
|
|
- name: Health
|
|
description: Liveness probe
|
|
- name: Families
|
|
description: Botanical families (e.g. Fabaceae, Rosaceae)
|
|
- name: Species
|
|
description: Plant species within a family
|
|
- name: Cultivars
|
|
description: Named cultivars / varieties of a species
|
|
- name: Suppliers
|
|
description: Seed and plant suppliers
|
|
- name: Cultivar-Suppliers
|
|
description: Link cultivars to suppliers with pricing info
|
|
- name: Companions
|
|
description: Companion planting relationships between species
|
|
- name: Images
|
|
description: Entity images stored in S3
|
|
- name: Search
|
|
description: Full-text search across all entities
|
|
- name: Auth
|
|
description: OIDC authentication (Authentik)
|
|
|
|
paths:
|
|
# ── Health ──────────────────────────────────────────────────────────
|
|
/health:
|
|
get:
|
|
tags: [Health]
|
|
summary: Health check
|
|
operationId: health
|
|
responses:
|
|
"200":
|
|
description: Service is healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: ok
|
|
|
|
# ── Families ────────────────────────────────────────────────────────
|
|
/api/v1/families:
|
|
get:
|
|
tags: [Families]
|
|
summary: List families (paginated)
|
|
operationId: listFamilies
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- $ref: "#/components/parameters/search"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of families
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PaginatedFamilies"
|
|
post:
|
|
tags: [Families]
|
|
summary: Create a family
|
|
operationId: createFamily
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateFamily"
|
|
responses:
|
|
"200":
|
|
description: Created family
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Family"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/families/{ref}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Families]
|
|
summary: Get a family by slug or UUID
|
|
operationId: getFamily
|
|
responses:
|
|
"200":
|
|
description: Family details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Family"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Families]
|
|
summary: Update a family
|
|
operationId: updateFamily
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateFamily"
|
|
responses:
|
|
"200":
|
|
description: Updated family
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Family"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
delete:
|
|
tags: [Families]
|
|
summary: Delete a family (admin only)
|
|
operationId: deleteFamily
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ── Species ─────────────────────────────────────────────────────────
|
|
/api/v1/species:
|
|
get:
|
|
tags: [Species]
|
|
summary: List species (paginated, filterable)
|
|
operationId: listSpecies
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- $ref: "#/components/parameters/search"
|
|
- name: family
|
|
in: query
|
|
description: Filter by family slug
|
|
schema:
|
|
type: string
|
|
- name: plant_layer
|
|
in: query
|
|
description: "Filter by plant layer (e.g. canopy, shrub, herb, ground_cover, climber, root)"
|
|
schema:
|
|
type: string
|
|
- name: nitrogen_fixer
|
|
in: query
|
|
description: Filter nitrogen-fixing species
|
|
schema:
|
|
type: boolean
|
|
- name: dynamic_accumulator
|
|
in: query
|
|
description: Filter dynamic accumulator species
|
|
schema:
|
|
type: boolean
|
|
- name: drought_tolerance
|
|
in: query
|
|
description: Filter by drought tolerance level
|
|
schema:
|
|
type: string
|
|
- name: native_status
|
|
in: query
|
|
description: Filter by native status
|
|
schema:
|
|
type: string
|
|
- name: min_nectar
|
|
in: query
|
|
description: Minimum nectar value (0-4)
|
|
schema:
|
|
type: integer
|
|
format: int16
|
|
- name: min_bees
|
|
in: query
|
|
description: Minimum wild bee count
|
|
schema:
|
|
type: integer
|
|
format: int32
|
|
responses:
|
|
"200":
|
|
description: Paginated list of species
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PaginatedSpecies"
|
|
post:
|
|
tags: [Species]
|
|
summary: Create a species
|
|
operationId: createSpecies
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateSpecies"
|
|
responses:
|
|
"200":
|
|
description: Created species
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Species"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/species/{ref}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Species]
|
|
summary: Get a species by slug or UUID
|
|
operationId: getSpecies
|
|
responses:
|
|
"200":
|
|
description: Species details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Species"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Species]
|
|
summary: Update a species
|
|
operationId: updateSpecies
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateSpecies"
|
|
responses:
|
|
"200":
|
|
description: Updated species
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Species"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
delete:
|
|
tags: [Species]
|
|
summary: Delete a species (admin only)
|
|
operationId: deleteSpecies
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/api/v1/species/{ref}/companions:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Companions]
|
|
summary: List companion relationships for a species
|
|
operationId: listCompanionsForSpecies
|
|
responses:
|
|
"200":
|
|
description: List of companion relationships
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CompanionRelationship"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ── Cultivars ───────────────────────────────────────────────────────
|
|
/api/v1/cultivars:
|
|
get:
|
|
tags: [Cultivars]
|
|
summary: List cultivars (paginated)
|
|
operationId: listCultivars
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- $ref: "#/components/parameters/search"
|
|
- name: species
|
|
in: query
|
|
description: Filter by species slug
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Paginated list of cultivars
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PaginatedCultivars"
|
|
post:
|
|
tags: [Cultivars]
|
|
summary: Create a cultivar
|
|
operationId: createCultivar
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateCultivar"
|
|
responses:
|
|
"200":
|
|
description: Created cultivar
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Cultivar"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/cultivars/{ref}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Cultivars]
|
|
summary: Get a cultivar by slug or UUID
|
|
operationId: getCultivar
|
|
responses:
|
|
"200":
|
|
description: Cultivar details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Cultivar"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Cultivars]
|
|
summary: Update a cultivar
|
|
operationId: updateCultivar
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateCultivar"
|
|
responses:
|
|
"200":
|
|
description: Updated cultivar
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Cultivar"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
delete:
|
|
tags: [Cultivars]
|
|
summary: Delete a cultivar (admin only)
|
|
operationId: deleteCultivar
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
/api/v1/cultivars/{ref}/suppliers:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Cultivar-Suppliers]
|
|
summary: List suppliers linked to a cultivar
|
|
operationId: listCultivarSuppliers
|
|
responses:
|
|
"200":
|
|
description: List of cultivar-supplier links
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CultivarSupplier"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
post:
|
|
tags: [Cultivar-Suppliers]
|
|
summary: Link a supplier to a cultivar
|
|
operationId: linkCultivarSupplier
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateCultivarSupplier"
|
|
responses:
|
|
"200":
|
|
description: Created link
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CultivarSupplier"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/cultivars/{cid}/suppliers/{sid}:
|
|
parameters:
|
|
- name: cid
|
|
in: path
|
|
required: true
|
|
description: Cultivar UUID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: sid
|
|
in: path
|
|
required: true
|
|
description: Supplier UUID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
delete:
|
|
tags: [Cultivar-Suppliers]
|
|
summary: Unlink a supplier from a cultivar
|
|
operationId: unlinkCultivarSupplier
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
# ── Suppliers ───────────────────────────────────────────────────────
|
|
/api/v1/suppliers:
|
|
get:
|
|
tags: [Suppliers]
|
|
summary: List all suppliers
|
|
operationId: listSuppliers
|
|
responses:
|
|
"200":
|
|
description: List of suppliers
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Supplier"
|
|
post:
|
|
tags: [Suppliers]
|
|
summary: Create a supplier
|
|
operationId: createSupplier
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateSupplier"
|
|
responses:
|
|
"200":
|
|
description: Created supplier
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Supplier"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/suppliers/{ref}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ref"
|
|
get:
|
|
tags: [Suppliers]
|
|
summary: Get a supplier by slug or UUID
|
|
operationId: getSupplier
|
|
responses:
|
|
"200":
|
|
description: Supplier details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Supplier"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Suppliers]
|
|
summary: Update a supplier
|
|
operationId: updateSupplier
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateSupplier"
|
|
responses:
|
|
"200":
|
|
description: Updated supplier
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Supplier"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
delete:
|
|
tags: [Suppliers]
|
|
summary: Delete a supplier (admin only)
|
|
operationId: deleteSupplier
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ── Companions ──────────────────────────────────────────────────────
|
|
/api/v1/companions:
|
|
get:
|
|
tags: [Companions]
|
|
summary: List all companion relationships (with species names)
|
|
operationId: listCompanions
|
|
responses:
|
|
"200":
|
|
description: List of all companion relationships with resolved species names
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CompanionWithNames"
|
|
post:
|
|
tags: [Companions]
|
|
summary: Create a companion relationship
|
|
operationId: createCompanion
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateCompanion"
|
|
responses:
|
|
"200":
|
|
description: Created relationship
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CompanionRelationship"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/companions/{id}:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: Companion relationship UUID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
delete:
|
|
tags: [Companions]
|
|
summary: Delete a companion relationship
|
|
operationId: deleteCompanion
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
# ── Images ──────────────────────────────────────────────────────────
|
|
/api/v1/images/{entity_type}/{entity_id}:
|
|
parameters:
|
|
- name: entity_type
|
|
in: path
|
|
required: true
|
|
description: "Entity type (family, species, cultivar)"
|
|
schema:
|
|
type: string
|
|
enum: [family, species, cultivar]
|
|
- name: entity_id
|
|
in: path
|
|
required: true
|
|
description: Entity UUID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
get:
|
|
tags: [Images]
|
|
summary: List images for an entity
|
|
operationId: listImagesForEntity
|
|
responses:
|
|
"200":
|
|
description: List of images
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Image"
|
|
|
|
/api/v1/images:
|
|
post:
|
|
tags: [Images]
|
|
summary: Upload an image (multipart/form-data)
|
|
operationId: uploadImage
|
|
security:
|
|
- session: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [entity_type, entity_id, file]
|
|
properties:
|
|
entity_type:
|
|
type: string
|
|
enum: [family, species, cultivar]
|
|
entity_id:
|
|
type: string
|
|
format: uuid
|
|
file:
|
|
type: string
|
|
format: binary
|
|
caption:
|
|
type: string
|
|
source_url:
|
|
type: string
|
|
license:
|
|
type: string
|
|
is_primary:
|
|
type: string
|
|
enum: ["true", "false", "1", "0"]
|
|
responses:
|
|
"200":
|
|
description: Uploaded image metadata
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Image"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/v1/images/{id}:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: Image UUID
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
delete:
|
|
tags: [Images]
|
|
summary: Delete an image (admin only)
|
|
operationId: deleteImage
|
|
security:
|
|
- session: []
|
|
responses:
|
|
"200":
|
|
$ref: "#/components/responses/Deleted"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/img/{path}:
|
|
parameters:
|
|
- name: path
|
|
in: path
|
|
required: true
|
|
description: S3 object key (e.g. species/uuid/image.jpg)
|
|
schema:
|
|
type: string
|
|
get:
|
|
tags: [Images]
|
|
summary: Serve an image from S3
|
|
operationId: serveImage
|
|
responses:
|
|
"200":
|
|
description: Image binary data
|
|
content:
|
|
image/jpeg:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
image/png:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
image/webp:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
"404":
|
|
description: Image not found
|
|
|
|
# ── Search ──────────────────────────────────────────────────────────
|
|
/api/v1/search:
|
|
get:
|
|
tags: [Search]
|
|
summary: Full-text search across families, species and cultivars
|
|
operationId: search
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: true
|
|
description: Search query (whitespace-separated terms, ANDed)
|
|
schema:
|
|
type: string
|
|
- name: limit
|
|
in: query
|
|
description: Maximum results (default 20, max 100)
|
|
schema:
|
|
type: integer
|
|
default: 20
|
|
maximum: 100
|
|
responses:
|
|
"200":
|
|
description: Search results ranked by relevance
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/SearchResult"
|
|
|
|
# ── Auth ────────────────────────────────────────────────────────────
|
|
/auth/oidc/login:
|
|
get:
|
|
tags: [Auth]
|
|
summary: Initiate OIDC login (redirects to Authentik)
|
|
operationId: oidcLogin
|
|
responses:
|
|
"302":
|
|
description: Redirect to Authentik authorization endpoint
|
|
|
|
/auth/oidc/callback:
|
|
get:
|
|
tags: [Auth]
|
|
summary: OIDC callback (handles code exchange)
|
|
operationId: oidcCallback
|
|
parameters:
|
|
- name: code
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- name: state
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"302":
|
|
description: Redirect to / after successful login
|
|
|
|
/auth/oidc/logout:
|
|
get:
|
|
tags: [Auth]
|
|
summary: Logout (destroys session)
|
|
operationId: oidcLogout
|
|
responses:
|
|
"302":
|
|
description: Redirect to /
|
|
|
|
/auth/me:
|
|
get:
|
|
tags: [Auth]
|
|
summary: Get current user info
|
|
operationId: me
|
|
responses:
|
|
"200":
|
|
description: Current authenticated user
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MeResponse"
|
|
"401":
|
|
description: Not authenticated
|
|
|
|
components:
|
|
securitySchemes:
|
|
session:
|
|
type: apiKey
|
|
in: cookie
|
|
name: id
|
|
description: Session cookie set after OIDC login
|
|
|
|
parameters:
|
|
page:
|
|
name: page
|
|
in: query
|
|
description: Page number (1-based, default 1)
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
minimum: 1
|
|
per_page:
|
|
name: per_page
|
|
in: query
|
|
description: Items per page (default 25, max 100)
|
|
schema:
|
|
type: integer
|
|
default: 25
|
|
maximum: 100
|
|
search:
|
|
name: search
|
|
in: query
|
|
description: Full-text search query
|
|
schema:
|
|
type: string
|
|
ref:
|
|
name: ref
|
|
in: path
|
|
required: true
|
|
description: Slug or UUID
|
|
schema:
|
|
type: string
|
|
|
|
responses:
|
|
NotFound:
|
|
description: Entity not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
Forbidden:
|
|
description: Insufficient permissions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
Deleted:
|
|
description: Successfully deleted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
deleted:
|
|
type: boolean
|
|
example: true
|
|
|
|
schemas:
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
required: [error]
|
|
|
|
# ── Family ──────────────────────────────────────────────────────
|
|
Family:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
slug:
|
|
type: string
|
|
name_scientific:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
nullable: true
|
|
name_de:
|
|
type: string
|
|
nullable: true
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateFamily:
|
|
type: object
|
|
required: [name_scientific]
|
|
properties:
|
|
name_scientific:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
name_de:
|
|
type: string
|
|
description:
|
|
type: string
|
|
|
|
UpdateFamily:
|
|
type: object
|
|
properties:
|
|
name_scientific:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
name_de:
|
|
type: string
|
|
description:
|
|
type: string
|
|
|
|
PaginatedFamilies:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Family"
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
per_page:
|
|
type: integer
|
|
|
|
# ── Species ─────────────────────────────────────────────────────
|
|
Species:
|
|
type: object
|
|
description: Full species record with all ecological and agronomic fields
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
slug:
|
|
type: string
|
|
family_id:
|
|
type: string
|
|
format: uuid
|
|
name_scientific:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
nullable: true
|
|
name_de:
|
|
type: string
|
|
nullable: true
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
description_de:
|
|
type: string
|
|
nullable: true
|
|
description_en:
|
|
type: string
|
|
nullable: true
|
|
soil_moisture:
|
|
type: string
|
|
nullable: true
|
|
drainage_requirement:
|
|
type: string
|
|
nullable: true
|
|
organic_matter_pct:
|
|
type: number
|
|
nullable: true
|
|
nitrogen_ppm:
|
|
type: integer
|
|
nullable: true
|
|
phosphorus_ppm:
|
|
type: integer
|
|
nullable: true
|
|
potassium_ppm:
|
|
type: integer
|
|
nullable: true
|
|
boron_ppm:
|
|
type: number
|
|
nullable: true
|
|
calcium_ppm:
|
|
type: integer
|
|
nullable: true
|
|
copper_ppm:
|
|
type: number
|
|
nullable: true
|
|
iron_ppm:
|
|
type: number
|
|
nullable: true
|
|
magnesium_ppm:
|
|
type: integer
|
|
nullable: true
|
|
manganese_ppm:
|
|
type: number
|
|
nullable: true
|
|
molybdenum_ppm:
|
|
type: number
|
|
nullable: true
|
|
sulfur_ppm:
|
|
type: integer
|
|
nullable: true
|
|
zinc_ppm:
|
|
type: number
|
|
nullable: true
|
|
ph_min:
|
|
type: number
|
|
nullable: true
|
|
ph_max:
|
|
type: number
|
|
nullable: true
|
|
soil_texture_preference:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
hardiness_zone_usda:
|
|
type: string
|
|
nullable: true
|
|
hardiness_zone_at:
|
|
type: string
|
|
nullable: true
|
|
min_temp:
|
|
type: number
|
|
nullable: true
|
|
max_temp:
|
|
type: number
|
|
nullable: true
|
|
drought_tolerance:
|
|
type: string
|
|
nullable: true
|
|
water_requirement_mm_week:
|
|
type: number
|
|
nullable: true
|
|
waterlogging_tolerance:
|
|
type: boolean
|
|
nullable: true
|
|
salt_tolerance:
|
|
type: string
|
|
nullable: true
|
|
edibility_rating:
|
|
type: integer
|
|
nullable: true
|
|
food_uses:
|
|
type: string
|
|
nullable: true
|
|
food_uses_de:
|
|
type: string
|
|
nullable: true
|
|
food_uses_en:
|
|
type: string
|
|
nullable: true
|
|
medicinal_uses:
|
|
type: string
|
|
nullable: true
|
|
medicinal_uses_de:
|
|
type: string
|
|
nullable: true
|
|
medicinal_uses_en:
|
|
type: string
|
|
nullable: true
|
|
other_uses:
|
|
type: string
|
|
nullable: true
|
|
other_uses_de:
|
|
type: string
|
|
nullable: true
|
|
other_uses_en:
|
|
type: string
|
|
nullable: true
|
|
native_range:
|
|
type: string
|
|
nullable: true
|
|
native_range_de:
|
|
type: string
|
|
nullable: true
|
|
native_range_en:
|
|
type: string
|
|
nullable: true
|
|
invasiveness:
|
|
type: string
|
|
nullable: true
|
|
pollination_type:
|
|
type: string
|
|
nullable: true
|
|
plant_layer:
|
|
type: string
|
|
nullable: true
|
|
nitrogen_fixer:
|
|
type: boolean
|
|
nullable: true
|
|
dynamic_accumulator:
|
|
type: boolean
|
|
nullable: true
|
|
dynamic_accumulator_nutrients:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
attracts_pollinators:
|
|
type: boolean
|
|
nullable: true
|
|
attracts_beneficial_insects:
|
|
type: boolean
|
|
nullable: true
|
|
wildlife_value:
|
|
type: string
|
|
nullable: true
|
|
wildlife_value_de:
|
|
type: string
|
|
nullable: true
|
|
wildlife_value_en:
|
|
type: string
|
|
nullable: true
|
|
mulch_plant:
|
|
type: boolean
|
|
nullable: true
|
|
ground_cover_quality:
|
|
type: string
|
|
nullable: true
|
|
allelopathic:
|
|
type: boolean
|
|
nullable: true
|
|
guild_role:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
succession_stage:
|
|
type: string
|
|
nullable: true
|
|
heavy_metal_tolerance:
|
|
type: boolean
|
|
nullable: true
|
|
wikidata_qid:
|
|
type: string
|
|
nullable: true
|
|
gbif_id:
|
|
type: string
|
|
nullable: true
|
|
eppo_code:
|
|
type: string
|
|
nullable: true
|
|
pfaf_url:
|
|
type: string
|
|
nullable: true
|
|
nectar_value:
|
|
type: integer
|
|
nullable: true
|
|
pollen_value:
|
|
type: integer
|
|
nullable: true
|
|
wild_bee_count:
|
|
type: integer
|
|
nullable: true
|
|
wild_bee_specialist_count:
|
|
type: integer
|
|
nullable: true
|
|
butterfly_moth_count:
|
|
type: integer
|
|
nullable: true
|
|
caterpillar_host_count:
|
|
type: integer
|
|
nullable: true
|
|
caterpillar_specialist_count:
|
|
type: integer
|
|
nullable: true
|
|
hoverfly_count:
|
|
type: integer
|
|
nullable: true
|
|
beetle_count:
|
|
type: integer
|
|
nullable: true
|
|
bird_count:
|
|
type: integer
|
|
nullable: true
|
|
mammal_count:
|
|
type: integer
|
|
nullable: true
|
|
native_status:
|
|
type: string
|
|
nullable: true
|
|
naturadb_tags:
|
|
type: string
|
|
nullable: true
|
|
primary_image_key:
|
|
type: string
|
|
nullable: true
|
|
source_urls:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateSpecies:
|
|
type: object
|
|
required: [family_id, name_scientific]
|
|
properties:
|
|
family_id:
|
|
type: string
|
|
format: uuid
|
|
name_scientific:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
name_de:
|
|
type: string
|
|
description:
|
|
type: string
|
|
description_de:
|
|
type: string
|
|
description_en:
|
|
type: string
|
|
plant_layer:
|
|
type: string
|
|
nitrogen_fixer:
|
|
type: boolean
|
|
dynamic_accumulator:
|
|
type: boolean
|
|
dynamic_accumulator_nutrients:
|
|
type: array
|
|
items:
|
|
type: string
|
|
drought_tolerance:
|
|
type: string
|
|
native_status:
|
|
type: string
|
|
nectar_value:
|
|
type: integer
|
|
pollen_value:
|
|
type: integer
|
|
ph_min:
|
|
type: number
|
|
ph_max:
|
|
type: number
|
|
soil_texture_preference:
|
|
type: array
|
|
items:
|
|
type: string
|
|
hardiness_zone_usda:
|
|
type: string
|
|
hardiness_zone_at:
|
|
type: string
|
|
min_temp:
|
|
type: number
|
|
max_temp:
|
|
type: number
|
|
salt_tolerance:
|
|
type: string
|
|
edibility_rating:
|
|
type: integer
|
|
food_uses:
|
|
type: string
|
|
food_uses_de:
|
|
type: string
|
|
food_uses_en:
|
|
type: string
|
|
medicinal_uses:
|
|
type: string
|
|
medicinal_uses_de:
|
|
type: string
|
|
medicinal_uses_en:
|
|
type: string
|
|
other_uses:
|
|
type: string
|
|
other_uses_de:
|
|
type: string
|
|
other_uses_en:
|
|
type: string
|
|
native_range:
|
|
type: string
|
|
native_range_de:
|
|
type: string
|
|
native_range_en:
|
|
type: string
|
|
invasiveness:
|
|
type: string
|
|
pollination_type:
|
|
type: string
|
|
attracts_pollinators:
|
|
type: boolean
|
|
attracts_beneficial_insects:
|
|
type: boolean
|
|
wildlife_value:
|
|
type: string
|
|
wildlife_value_de:
|
|
type: string
|
|
wildlife_value_en:
|
|
type: string
|
|
mulch_plant:
|
|
type: boolean
|
|
ground_cover_quality:
|
|
type: string
|
|
allelopathic:
|
|
type: boolean
|
|
guild_role:
|
|
type: array
|
|
items:
|
|
type: string
|
|
succession_stage:
|
|
type: string
|
|
heavy_metal_tolerance:
|
|
type: boolean
|
|
wikidata_qid:
|
|
type: string
|
|
gbif_id:
|
|
type: string
|
|
eppo_code:
|
|
type: string
|
|
pfaf_url:
|
|
type: string
|
|
wild_bee_count:
|
|
type: integer
|
|
wild_bee_specialist_count:
|
|
type: integer
|
|
butterfly_moth_count:
|
|
type: integer
|
|
caterpillar_host_count:
|
|
type: integer
|
|
caterpillar_specialist_count:
|
|
type: integer
|
|
hoverfly_count:
|
|
type: integer
|
|
beetle_count:
|
|
type: integer
|
|
bird_count:
|
|
type: integer
|
|
mammal_count:
|
|
type: integer
|
|
naturadb_tags:
|
|
type: string
|
|
source_urls:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
PaginatedSpecies:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Species"
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
per_page:
|
|
type: integer
|
|
|
|
# ── Cultivar ────────────────────────────────────────────────────
|
|
Cultivar:
|
|
type: object
|
|
description: A named cultivar/variety with growing parameters and scheduling
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
slug:
|
|
type: string
|
|
species_id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
nullable: true
|
|
name_de:
|
|
type: string
|
|
nullable: true
|
|
name_scientific:
|
|
type: string
|
|
nullable: true
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
description_de:
|
|
type: string
|
|
nullable: true
|
|
description_en:
|
|
type: string
|
|
nullable: true
|
|
is_organic:
|
|
type: boolean
|
|
perennial:
|
|
type: boolean
|
|
growing_time_days:
|
|
type: integer
|
|
nullable: true
|
|
planting_depth_cm:
|
|
type: number
|
|
nullable: true
|
|
row_spacing_cm:
|
|
type: number
|
|
nullable: true
|
|
plant_spacing_cm:
|
|
type: number
|
|
nullable: true
|
|
days_to_germination:
|
|
type: integer
|
|
nullable: true
|
|
germination_temp_c:
|
|
type: number
|
|
nullable: true
|
|
light_requirement:
|
|
type: string
|
|
nullable: true
|
|
stratification_required:
|
|
type: boolean
|
|
nullable: true
|
|
stratification_days:
|
|
type: integer
|
|
nullable: true
|
|
scarification_required:
|
|
type: boolean
|
|
nullable: true
|
|
seed_viability_years:
|
|
type: integer
|
|
nullable: true
|
|
storage_temp_c:
|
|
type: number
|
|
nullable: true
|
|
storage_humidity:
|
|
type: string
|
|
nullable: true
|
|
storage_notes:
|
|
type: string
|
|
nullable: true
|
|
min_temp:
|
|
type: number
|
|
nullable: true
|
|
max_temp:
|
|
type: number
|
|
nullable: true
|
|
humidity:
|
|
type: string
|
|
nullable: true
|
|
light:
|
|
type: string
|
|
nullable: true
|
|
frost_tolerance:
|
|
type: string
|
|
nullable: true
|
|
min_light_hours_day:
|
|
type: number
|
|
nullable: true
|
|
optimal_light_hours_day:
|
|
type: number
|
|
nullable: true
|
|
greenhouse_min_temp_c:
|
|
type: number
|
|
nullable: true
|
|
indoor_season_extension_weeks:
|
|
type: integer
|
|
nullable: true
|
|
ventilation_requirement:
|
|
type: string
|
|
nullable: true
|
|
heating_required:
|
|
type: boolean
|
|
nullable: true
|
|
indoor_sowing_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
description: Month numbers (1-12)
|
|
direct_sowing_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
transplanting_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
glasshouse_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
harvesting_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
indoor_sowing_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
description: ISO week numbers (1-52)
|
|
direct_sowing_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
transplanting_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
glasshouse_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
harvesting_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
nullable: true
|
|
succession_planting_days:
|
|
type: integer
|
|
nullable: true
|
|
planting_notes:
|
|
type: string
|
|
nullable: true
|
|
propagation_methods:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
cutting_season:
|
|
type: string
|
|
nullable: true
|
|
rootstock_species_id:
|
|
type: string
|
|
format: uuid
|
|
nullable: true
|
|
years_to_first_harvest:
|
|
type: integer
|
|
nullable: true
|
|
productive_lifespan_years:
|
|
type: integer
|
|
nullable: true
|
|
expected_yield_kg_per_m2:
|
|
type: number
|
|
nullable: true
|
|
yield_unit:
|
|
type: string
|
|
nullable: true
|
|
expected_yield_value:
|
|
type: number
|
|
nullable: true
|
|
harvest_window_days:
|
|
type: integer
|
|
nullable: true
|
|
storage_method:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
shelf_life_days:
|
|
type: integer
|
|
nullable: true
|
|
cold_storage_days:
|
|
type: integer
|
|
nullable: true
|
|
pollination_group:
|
|
type: string
|
|
nullable: true
|
|
self_fertile:
|
|
type: boolean
|
|
nullable: true
|
|
rootstock_compatibility:
|
|
type: string
|
|
nullable: true
|
|
wikidata_qid:
|
|
type: string
|
|
nullable: true
|
|
gbif_id:
|
|
type: string
|
|
nullable: true
|
|
pfaf_url:
|
|
type: string
|
|
nullable: true
|
|
primary_image_key:
|
|
type: string
|
|
nullable: true
|
|
source_urls:
|
|
type: array
|
|
items:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateCultivar:
|
|
type: object
|
|
required: [species_id, name]
|
|
properties:
|
|
species_id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
name_en:
|
|
type: string
|
|
name_de:
|
|
type: string
|
|
name_scientific:
|
|
type: string
|
|
description:
|
|
type: string
|
|
description_de:
|
|
type: string
|
|
description_en:
|
|
type: string
|
|
is_organic:
|
|
type: boolean
|
|
perennial:
|
|
type: boolean
|
|
growing_time_days:
|
|
type: integer
|
|
planting_depth_cm:
|
|
type: number
|
|
row_spacing_cm:
|
|
type: number
|
|
plant_spacing_cm:
|
|
type: number
|
|
days_to_germination:
|
|
type: integer
|
|
germination_temp_c:
|
|
type: number
|
|
light_requirement:
|
|
type: string
|
|
indoor_sowing_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
direct_sowing_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
transplanting_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
glasshouse_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
harvesting_months:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
indoor_sowing_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
direct_sowing_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
transplanting_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
glasshouse_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
harvesting_weeks:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
source_urls:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
PaginatedCultivars:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Cultivar"
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
per_page:
|
|
type: integer
|
|
|
|
# ── Supplier ────────────────────────────────────────────────────
|
|
Supplier:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
slug:
|
|
type: string
|
|
name:
|
|
type: string
|
|
url:
|
|
type: string
|
|
nullable: true
|
|
is_organic:
|
|
type: boolean
|
|
is_demeter:
|
|
type: boolean
|
|
country:
|
|
type: string
|
|
nullable: true
|
|
notes:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateSupplier:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
url:
|
|
type: string
|
|
is_organic:
|
|
type: boolean
|
|
is_demeter:
|
|
type: boolean
|
|
country:
|
|
type: string
|
|
notes:
|
|
type: string
|
|
|
|
CultivarSupplier:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
cultivar_id:
|
|
type: string
|
|
format: uuid
|
|
supplier_id:
|
|
type: string
|
|
format: uuid
|
|
article_number:
|
|
type: string
|
|
nullable: true
|
|
product_url:
|
|
type: string
|
|
nullable: true
|
|
price_eur:
|
|
type: number
|
|
nullable: true
|
|
pack_size:
|
|
type: number
|
|
nullable: true
|
|
pack_unit:
|
|
type: string
|
|
nullable: true
|
|
last_checked_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CreateCultivarSupplier:
|
|
type: object
|
|
required: [supplier_id]
|
|
properties:
|
|
supplier_id:
|
|
type: string
|
|
format: uuid
|
|
article_number:
|
|
type: string
|
|
product_url:
|
|
type: string
|
|
price_eur:
|
|
type: number
|
|
pack_size:
|
|
type: number
|
|
pack_unit:
|
|
type: string
|
|
|
|
# ── Companions ──────────────────────────────────────────────────
|
|
CompanionRelationship:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
species_a_id:
|
|
type: string
|
|
format: uuid
|
|
species_b_id:
|
|
type: string
|
|
format: uuid
|
|
relationship:
|
|
type: string
|
|
description: "e.g. beneficial, antagonistic, neutral"
|
|
mechanism:
|
|
type: string
|
|
nullable: true
|
|
source_url:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
CompanionWithNames:
|
|
type: object
|
|
description: Companion relationship with resolved species names and images
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
species_a_id:
|
|
type: string
|
|
format: uuid
|
|
species_b_id:
|
|
type: string
|
|
format: uuid
|
|
relationship:
|
|
type: string
|
|
mechanism:
|
|
type: string
|
|
nullable: true
|
|
source_url:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
species_a_scientific:
|
|
type: string
|
|
species_a_de:
|
|
type: string
|
|
nullable: true
|
|
species_a_slug:
|
|
type: string
|
|
species_a_image:
|
|
type: string
|
|
nullable: true
|
|
species_b_scientific:
|
|
type: string
|
|
species_b_de:
|
|
type: string
|
|
nullable: true
|
|
species_b_slug:
|
|
type: string
|
|
species_b_image:
|
|
type: string
|
|
nullable: true
|
|
|
|
CreateCompanion:
|
|
type: object
|
|
required: [species_a_id, species_b_id, relationship]
|
|
properties:
|
|
species_a_id:
|
|
type: string
|
|
format: uuid
|
|
species_b_id:
|
|
type: string
|
|
format: uuid
|
|
relationship:
|
|
type: string
|
|
mechanism:
|
|
type: string
|
|
source_url:
|
|
type: string
|
|
|
|
# ── Images ──────────────────────────────────────────────────────
|
|
Image:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
entity_type:
|
|
type: string
|
|
entity_id:
|
|
type: string
|
|
format: uuid
|
|
s3_key:
|
|
type: string
|
|
caption:
|
|
type: string
|
|
nullable: true
|
|
source_url:
|
|
type: string
|
|
nullable: true
|
|
license:
|
|
type: string
|
|
nullable: true
|
|
is_primary:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ── Search ──────────────────────────────────────────────────────
|
|
SearchResult:
|
|
type: object
|
|
properties:
|
|
entity_type:
|
|
type: string
|
|
description: "family, species, or cultivar"
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
slug:
|
|
type: string
|
|
name:
|
|
type: string
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
rank:
|
|
type: number
|
|
description: Full-text search relevance score
|
|
|
|
# ── Auth ────────────────────────────────────────────────────────
|
|
MeResponse:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
email:
|
|
type: string
|
|
name:
|
|
type: string
|
|
nullable: true
|
|
nickname:
|
|
type: string
|
|
nullable: true
|
|
admin:
|
|
type: boolean
|