Files
famapp/docs/api/openapi.yaml
T

830 lines
23 KiB
YAML

openapi: 3.1.0
info:
title: famapp API
version: "1.0.0"
description: |
Household-scoped REST JSON API for famapp (fam.ginnoir.com).
Authenticate with an Auth.js session cookie (browser) or a household API bearer token
(external clients). Generate bearer tokens in Settings → API tokens.
servers:
- url: https://fam.ginnoir.com
description: Production
- url: http://localhost:3000
description: Local development
security:
- bearerAuth: []
- sessionCookie: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Household API token from Settings → API tokens
sessionCookie:
type: apiKey
in: cookie
name: authjs.session-token
description: Auth.js session cookie (browser login)
schemas:
Error:
type: object
required: [error]
properties:
error:
type: string
OkResponse:
type: object
required: [ok]
properties:
ok:
type: boolean
enum: [true]
Calendar:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
color: { type: string, nullable: true }
visibility: { type: string, enum: [private, household] }
ownerId: { type: string, format: uuid }
CalendarInput:
type: object
required: [name]
properties:
name: { type: string }
color: { type: string, nullable: true }
visibility: { type: string, enum: [private, household], default: household }
CalendarEvent:
type: object
properties:
id: { type: string, format: uuid }
calendarId: { type: string, format: uuid }
title: { type: string }
startAt: { type: string, format: date-time }
endAt: { type: string, format: date-time }
allDay: { type: boolean }
location: { type: string, nullable: true }
notes: { type: string, nullable: true }
EventInput:
type: object
required: [calendarId, title, startAt, endAt]
properties:
calendarId: { type: string, format: uuid }
title: { type: string }
startAt: { type: string, format: date-time }
endAt: { type: string, format: date-time }
allDay: { type: boolean, default: false }
location: { type: string, nullable: true }
notes: { type: string, nullable: true }
remindMinutesBefore: { type: integer, nullable: true }
ListSummary:
type: object
properties:
id: { type: string, format: uuid }
type: { type: string }
name: { type: string }
archived: { type: boolean }
openCount: { type: integer }
doneCount: { type: integer }
createdAt: { type: string, format: date-time }
ListInput:
type: object
required: [type, name]
properties:
type: { type: string }
name: { type: string }
ListItem:
type: object
properties:
id: { type: string, format: uuid }
listId: { type: string, format: uuid }
text: { type: string }
qty: { type: string, nullable: true }
notes: { type: string, nullable: true }
done: { type: boolean }
dueAt: { type: string, format: date-time, nullable: true }
assigneeId: { type: string, format: uuid, nullable: true }
position: { type: integer }
ItemInput:
type: object
required: [text]
properties:
text: { type: string }
qty: { type: string, nullable: true }
notes: { type: string, nullable: true }
dueAt: { type: string, format: date-time, nullable: true }
assigneeId: { type: string, format: uuid, nullable: true }
metadata: { type: object, nullable: true }
Note:
type: object
properties:
id: { type: string, format: uuid }
householdId: { type: string, format: uuid }
authorId: { type: string, format: uuid }
title: { type: string }
body: { type: string }
pinned: { type: boolean }
remindAt: { type: string, format: date-time, nullable: true }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
NoteInput:
type: object
required: [title]
properties:
title: { type: string }
body: { type: string, default: "" }
pinned: { type: boolean, default: false }
remindAt: { type: string, format: date-time, nullable: true }
GardenContainer:
type: object
properties:
id: { type: string, format: uuid }
householdId: { type: string, format: uuid }
name: { type: string }
type: { type: string }
locationNotes: { type: string, nullable: true }
coverImageUrl: { type: string, nullable: true }
images: { type: array, items: { type: string } }
plantCount: { type: integer }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
GardenContainerDetail:
allOf:
- $ref: "#/components/schemas/GardenContainer"
- type: object
properties:
plants:
type: array
items:
$ref: "#/components/schemas/PlantSummary"
ContainerInput:
type: object
required: [name]
properties:
name: { type: string }
type: { type: string, default: other }
locationNotes: { type: string, nullable: true }
coverImageUrl: { type: string, nullable: true }
PlantSummary:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
scientificName: { type: string, nullable: true }
healthStatus: { type: string }
primaryImageUrl: { type: string, nullable: true }
category: { type: string }
PlantListItem:
type: object
properties:
id: { type: string, format: uuid }
containerId: { type: string, format: uuid, nullable: true }
containerName: { type: string, nullable: true }
name: { type: string }
healthStatus: { type: string }
primaryImageUrl: { type: string, nullable: true }
category: { type: string }
lastWateredAt: { type: string, format: date-time, nullable: true }
hasOverdueCare: { type: boolean }
PlantDetail:
type: object
properties:
id: { type: string, format: uuid }
householdId: { type: string, format: uuid }
containerId: { type: string, format: uuid, nullable: true }
containerName: { type: string, nullable: true }
name: { type: string }
scientificName: { type: string, nullable: true }
speciesId: { type: string, nullable: true }
category: { type: string }
notes: { type: string, nullable: true }
acquisitionDate: { type: string, nullable: true }
growthStage: { type: string, nullable: true }
healthStatus: { type: string }
sunlight: { type: string, nullable: true }
wateringNotes: { type: string, nullable: true }
fertilizingNotes: { type: string, nullable: true }
primaryImageUrl: { type: string, nullable: true }
images: { type: array, items: { type: string } }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
PlantInput:
type: object
required: [name]
properties:
name: { type: string }
category: { type: string, default: other }
containerId: { type: string, format: uuid, nullable: true }
healthStatus: { type: string, default: healthy }
growthStage: { type: string, nullable: true }
scientificName: { type: string, nullable: true }
speciesId: { type: string, nullable: true }
sunlight: { type: string, nullable: true }
wateringNotes: { type: string, nullable: true }
fertilizingNotes: { type: string, nullable: true }
notes: { type: string, nullable: true }
acquisitionDate: { type: string, nullable: true }
images: { type: array, items: { type: string }, default: [] }
primaryImageUrl: { type: string, nullable: true }
BangStats:
type: object
properties:
total: { type: integer }
recent:
type: array
items:
$ref: "#/components/schemas/BangSummary"
BangSummary:
type: object
properties:
id: { type: string, format: uuid }
occurredOn: { type: string, format: date, description: YYYY-MM-DD }
recordedByName: { type: string, nullable: true }
Bang:
type: object
properties:
id: { type: string, format: uuid }
occurredOn: { type: string, format: date }
recordedBy: { type: string, format: uuid, nullable: true }
createdAt: { type: string, format: date-time }
BangInput:
type: object
properties:
occurredOn:
type: string
format: date
description: YYYY-MM-DD; defaults to today
BangUpdateInput:
type: object
required: [occurredOn]
properties:
occurredOn: { type: string, format: date }
paths:
/api/v1/calendars:
get:
summary: List visible calendars
tags: [Calendars]
responses:
"200":
description: Calendar list
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Calendar" }
"401":
{
description: Unauthorized,
content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
}
post:
summary: Create calendar
tags: [Calendars]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CalendarInput" }
responses:
"201":
description: Created calendar
content:
application/json:
schema: { $ref: "#/components/schemas/Calendar" }
"401": { description: Unauthorized }
/api/v1/calendars/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get calendar
tags: [Calendars]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Calendar" }
"404": { description: Not found }
patch:
summary: Update calendar
tags: [Calendars]
requestBody:
content:
application/json:
schema:
type: object
properties:
name: { type: string }
visibility: { type: string, enum: [private, household] }
color: { type: string, nullable: true }
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Calendar" }
delete:
summary: Delete calendar
tags: [Calendars]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/events:
get:
summary: List events in date range
tags: [Events]
parameters:
- name: from
in: query
required: true
schema: { type: string, format: date-time }
- name: to
in: query
required: true
schema: { type: string, format: date-time }
- name: calendarIds
in: query
description: "all or comma-separated UUIDs"
schema: { type: string, default: all }
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/CalendarEvent" }
"400": { description: Missing from/to }
post:
summary: Create event
tags: [Events]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/EventInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/CalendarEvent" }
/api/v1/events/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get event
tags: [Events]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/CalendarEvent" }
patch:
summary: Update event
tags: [Events]
requestBody:
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/EventInput"
description: All fields optional for PATCH
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/CalendarEvent" }
delete:
summary: Delete event
tags: [Events]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/lists:
get:
summary: List lists
tags: [Lists]
parameters:
- name: type
in: query
schema: { type: string }
description: Filter by list type (e.g. shopping, tasks)
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/ListSummary" }
post:
summary: Create list
tags: [Lists]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ListInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/ListSummary" }
/api/v1/lists/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get list with items
tags: [Lists]
responses:
"200":
description: List detail including items array
patch:
summary: Update list name or archive
tags: [Lists]
requestBody:
content:
application/json:
schema:
type: object
properties:
name: { type: string }
archived: { type: boolean }
responses:
"200": { description: Updated list }
delete:
summary: Archive list
tags: [Lists]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/lists/{id}/items:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: List items
tags: [Lists]
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/ListItem" }
post:
summary: Add item
tags: [Lists]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ItemInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/ListItem" }
/api/v1/lists/{id}/items/{itemId}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
- name: itemId
in: path
required: true
schema: { type: string, format: uuid }
patch:
summary: Update or toggle item
tags: [Lists]
requestBody:
content:
application/json:
schema:
type: object
properties:
text: { type: string }
qty: { type: string, nullable: true }
notes: { type: string, nullable: true }
done: { type: boolean }
dueAt: { type: string, format: date-time, nullable: true }
responses:
"200": { description: Updated list with items }
delete:
summary: Delete item
tags: [Lists]
responses:
"200": { description: Updated list with items }
/api/v1/notes:
get:
summary: List notes
tags: [Notes]
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Note" }
post:
summary: Create note
tags: [Notes]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/NoteInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/Note" }
/api/v1/notes/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get note
tags: [Notes]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Note" }
patch:
summary: Update note
tags: [Notes]
requestBody:
content:
application/json:
schema:
type: object
properties:
title: { type: string }
body: { type: string }
pinned: { type: boolean }
remindAt: { type: string, format: date-time, nullable: true }
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Note" }
delete:
summary: Delete note
tags: [Notes]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/garden/containers:
get:
summary: List garden containers
tags: [Garden]
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/GardenContainer" }
post:
summary: Create container
tags: [Garden]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ContainerInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
/api/v1/garden/containers/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get container with plants
tags: [Garden]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
"404": { description: Not found }
patch:
summary: Update container
tags: [Garden]
requestBody:
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/ContainerInput"
description: All fields optional
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
delete:
summary: Delete container
tags: [Garden]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/garden/plants:
get:
summary: List plants
tags: [Garden]
parameters:
- name: containerId
in: query
schema: { type: string, format: uuid }
description: Filter by container
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/PlantListItem" }
post:
summary: Create plant
tags: [Garden]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PlantInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/PlantDetail" }
/api/v1/garden/plants/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
summary: Get plant detail
tags: [Garden]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/PlantDetail" }
"404": { description: Not found }
patch:
summary: Update plant
tags: [Garden]
requestBody:
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/PlantInput"
description: All fields optional
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/PlantDetail" }
delete:
summary: Delete plant
tags: [Garden]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
/api/v1/bangs:
get:
summary: Bang stats (total + recent)
tags: [Bangs]
parameters:
- name: limit
in: query
schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
description: Max recent entries to return
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/BangStats" }
post:
summary: Record a bang
tags: [Bangs]
requestBody:
content:
application/json:
schema: { $ref: "#/components/schemas/BangInput" }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/Bang" }
/api/v1/bangs/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
patch:
summary: Update bang date
tags: [Bangs]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/BangUpdateInput" }
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Bang" }
delete:
summary: Delete bang
tags: [Bangs]
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/OkResponse" }
tags:
- name: Calendars
- name: Events
- name: Lists
- name: Notes
- name: Garden
- name: Bangs