Files
florian.berthold 83ab8c4cf9 Add OpenAPI/Swagger docs at /api/docs
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.
2026-03-16 03:15:56 +01:00

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