{
  "openapi": "3.1.0",
  "info": {
    "title": "tataya Journal API",
    "version": "1.1.0",
    "description": "Read and log baby activities with durable, revocable family agent keys or caregiver sign-in sessions. Agent keys can be read-only or read-and-log. Timestamps include Z or a UTC offset; ongoing sleep edits use a revision check."
  },
  "servers": [
    {
      "url": "https://app.tataya.co",
      "description": "Production (default for the CLI)"
    },
    {
      "url": "http://localhost:8080",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/config": {
      "get": {
        "operationId": "getConfiguration",
        "summary": "Get public API configuration",
        "security": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_version": {
                      "type": "string"
                    },
                    "supabase_url": {
                      "type": "string"
                    },
                    "docs": {
                      "type": "string"
                    },
                    "openapi": {
                      "type": "string"
                    }
                  },
                  "required": []
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "operationId": "authenticate",
        "summary": "Sign in or refresh a caregiver session",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "password": {
                        "type": "string",
                        "writeOnly": true
                      }
                    },
                    "required": [
                      "email",
                      "password"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "refresh_token": {
                        "type": "string",
                        "writeOnly": true
                      }
                    },
                    "required": [
                      "refresh_token"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "integer"
                    },
                    "token_type": {
                      "const": "bearer"
                    }
                  },
                  "required": [
                    "access_token",
                    "refresh_token",
                    "expires_in"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing/expired sign-in credentials or an invalid/revoked agent key. Inspect error.code: unauthorized or invalid_agent_key; login also distinguishes credential and refresh failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Family access is denied, an agent key lacks write permission (insufficient_scope), or email confirmation is required during sign-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "getCurrentCaregiver",
        "summary": "Get authenticated user and family IDs",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "family_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "required": [
                    "user_id",
                    "family_id"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/expired sign-in credentials or an invalid/revoked agent key. Inspect error.code: unauthorized or invalid_agent_key; login also distinguishes credential and refresh failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Family access is denied, an agent key lacks write permission (insufficient_scope), or email confirmation is required during sign-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "operationId": "listActivities",
        "summary": "List family activities, newest first",
        "description": "Default window is the last 7 days. For stable pagination reuse the returned since/until bounds and next_cursor. Data is restricted to the current family.",
        "parameters": [
          {
            "in": "query",
            "name": "since",
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "ISO 8601 date-time with Z or an explicit UTC offset"
            }
          },
          {
            "in": "query",
            "name": "until",
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "ISO 8601 date-time with Z or an explicit UTC offset"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string",
              "description": "Opaque next_cursor from the previous page"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "since": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO 8601 date-time with Z or an explicit UTC offset"
                    },
                    "until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO 8601 date-time with Z or an explicit UTC offset"
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor",
                    "since",
                    "until"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing/expired sign-in credentials or an invalid/revoked agent key. Inspect error.code: unauthorized or invalid_agent_key; login also distinguishes credential and refresh failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Family access is denied, an agent key lacks write permission (insufficient_scope), or email confirmation is required during sign-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "logActivity",
        "summary": "Log a sleep, milk feed, solid food, or diaper activity",
        "description": "Use a stable Idempotency-Key per intended write. Keys are scoped to user, family and storage category. Different payloads with the same key return 409.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string",
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewEvent"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Activity"
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data",
                    "replayed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing/expired sign-in credentials or an invalid/revoked agent key. Inspect error.code: unauthorized or invalid_agent_key; login also distinguishes credential and refresh failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Family access is denied, an agent key lacks write permission (insufficient_scope), or email confirmation is required during sign-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Activity"
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data",
                    "replayed"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/events/sleep/{id}": {
      "patch": {
        "operationId": "endSleep",
        "summary": "End a sleep or correct sleep times/type",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "end_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "ISO 8601 date-time with Z or an explicit UTC offset"
                      }
                    },
                    "required": [
                      "end_at"
                    ]
                  },
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "sleep_type": {
                        "enum": [
                          "nap",
                          "night"
                        ]
                      },
                      "expected": {
                        "type": "object",
                        "additionalProperties": false,
                        "properties": {
                          "at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "end_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time",
                            "description": "Copy the current end_at from the sleep you read. Required for completed sleeps; null for ongoing sleeps."
                          },
                          "sleep_type": {
                            "enum": [
                              "nap",
                              "night"
                            ]
                          },
                          "revision": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "required": [
                          "at",
                          "sleep_type",
                          "revision"
                        ]
                      },
                      "end_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Corrected end time; omit to preserve the current end or ongoing state"
                      }
                    },
                    "required": [
                      "expected"
                    ],
                    "anyOf": [
                      {
                        "required": [
                          "at"
                        ]
                      },
                      {
                        "required": [
                          "end_at"
                        ]
                      },
                      {
                        "required": [
                          "sleep_type"
                        ]
                      }
                    ]
                  }
                ]
              },
              "examples": {
                "endOngoing": {
                  "summary": "End a running sleep",
                  "value": {
                    "end_at": "2026-11-13T03:00:00Z"
                  }
                },
                "correctCompleted": {
                  "summary": "Correct an already-ended sleep without replacing it",
                  "value": {
                    "end_at": "2026-11-13T02:30:00Z",
                    "expected": {
                      "at": "2026-11-12T19:00:00.000Z",
                      "end_at": "2026-11-13T03:00:00.000Z",
                      "sleep_type": "night",
                      "revision": 1
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Activity"
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data",
                    "replayed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing/expired sign-in credentials or an invalid/revoked agent key. Inspect error.code: unauthorized or invalid_agent_key; login also distinguishes credential and refresh failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Family access is denied, an agent key lacks write permission (insufficient_scope), or email confirmation is required during sign-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request failed; inspect error.code and error.message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "End an ongoing sleep with end_at alone. To correct an ongoing or completed sleep, read it first and send changed fields plus expected at, end_at, sleep_type and revision. expected.end_at is required for completed sleeps and is null for ongoing sleeps (legacy ongoing edits may omit it). Corrections preserve the ID and original author, recalculate duration and record the editor. A stale snapshot returns 409 concurrent_update; re-read before deciding whether to retry. Never delete and recreate a sleep to correct its times. Times need an explicit UTC offset; a correction cannot set a future start or end, or an end at/before its start. Null end_at cannot reopen a completed sleep."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tataya agent key or JWT",
        "description": "Use a durable agent key (created/revoked in web app Settings) or a current caregiver sign-in access token. Agent keys have no automatic expiry and remain limited to their family and selected permission."
      }
    },
    "schemas": {
      "NewEvent": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "const": "sleep"
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 date-time with Z or an explicit UTC offset"
              },
              "sleep_type": {
                "type": "string",
                "enum": [
                  "nap",
                  "night"
                ],
                "default": "nap"
              },
              "end_at": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 date-time with Z or an explicit UTC offset"
              }
            },
            "required": [
              "kind",
              "at"
            ]
          },
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "const": "milk"
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 date-time with Z or an explicit UTC offset"
              },
              "amount_ml": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2000
              },
              "notes": {
                "type": "string",
                "maxLength": 2000
              }
            },
            "required": [
              "kind",
              "at",
              "amount_ml"
            ]
          },
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "const": "solids"
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 date-time with Z or an explicit UTC offset"
              },
              "foods": {
                "type": "array",
                "minItems": 1,
                "maxItems": 30,
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100
                }
              },
              "notes": {
                "type": "string",
                "maxLength": 2000
              }
            },
            "required": [
              "kind",
              "at",
              "foods"
            ]
          },
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "const": "diaper"
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 date-time with Z or an explicit UTC offset"
              },
              "diaper_type": {
                "type": "string",
                "enum": [
                  "wet",
                  "dirty",
                  "both"
                ],
                "default": "dirty"
              },
              "notes": {
                "type": "string",
                "maxLength": 2000
              }
            },
            "required": [
              "kind",
              "at"
            ]
          }
        ]
      },
      "Activity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "enum": [
              "sleep",
              "milk",
              "solids",
              "diaper"
            ]
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 date-time with Z or an explicit UTC offset"
          },
          "end_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sleep_type": {
            "enum": [
              "nap",
              "night"
            ]
          },
          "duration_min": {
            "type": [
              "integer",
              "null"
            ]
          },
          "amount_ml": {
            "type": [
              "integer",
              "null"
            ]
          },
          "foods": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notes": {
            "type": "string"
          },
          "diaper_type": {
            "enum": [
              "wet",
              "dirty",
              "both"
            ]
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Sleep edit revision; read again before an ongoing edit."
          },
          "edited_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Caregiver who last edited the sleep; original author is preserved."
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "kind",
          "at"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
