{
  "openapi": "3.1.0",
  "info": {
    "title": "OweMeter API",
    "version": "0.1.0",
    "description": "The simplest way to measure who owes what. Split bills, track expenses, settle up. No awkward conversations. This document covers the public discovery surface plus representative protected endpoints used by the web app.",
    "contact": {
      "name": "OweMeter Support",
      "email": "support@owemeter.com",
      "url": "https://owemeter.com/contact"
    }
  },
  "servers": [
    {
      "url": "https://owemeter.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Human-readable API and discovery guide",
    "url": "https://owemeter.com/docs/api"
  },
  "tags": [
    {
      "name": "Public",
      "description": "Publicly discoverable endpoints."
    },
    {
      "name": "Protected",
      "description": "Endpoints that require an authenticated user session."
    },
    {
      "name": "Operations",
      "description": "Operational endpoints with stronger access requirements."
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol discovery and tool access."
    }
  ],
  "components": {
    "securitySchemes": {
      "ClerkOidc": {
        "type": "openIdConnect",
        "openIdConnectUrl": "https://owemeter.com/.well-known/openid-configuration"
      },
      "ClerkBearer": {
        "type": "oauth2",
        "description": "OAuth 2.1 / OIDC metadata for OweMeter APIs, mirrored on the app domain.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://clerk.owemeter.com/oauth/authorize",
            "tokenUrl": "https://clerk.owemeter.com/oauth/token",
            "scopes": {
              "openid": "OpenID Connect identity scope.",
              "profile": "Basic profile claims.",
              "email": "Email address claims."
            }
          }
        }
      },
      "CronSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-cron-secret",
        "description": "Shared secret required for scheduled jobs."
      }
    },
    "schemas": {
      "BlogFeedResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "properties": {
              "posts": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "total": {
                "type": "integer"
              },
              "page": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "totalPages": {
                "type": "integer"
              }
            },
            "additionalProperties": true
          }
        },
        "required": [
          "success"
        ]
      },
      "StatusResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "ok",
          "service",
          "version",
          "timestamp"
        ]
      },
      "McpJsonRpcRequest": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object"
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ]
      }
    }
  },
  "paths": {
    "/api/status": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Get service health and discovery metadata",
        "operationId": "getStatus",
        "responses": {
          "200": {
            "description": "Service status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/blog": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "List published blog posts",
        "operationId": "listBlogPosts",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional blog category slug."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated blog feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlogFeedResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp": {
      "get": {
        "tags": [
          "MCP"
        ],
        "summary": "Describe the MCP endpoint",
        "operationId": "describeMcp",
        "responses": {
          "200": {
            "description": "Basic endpoint description or SSE bootstrap response."
          }
        }
      },
      "post": {
        "tags": [
          "MCP"
        ],
        "summary": "Call the read-only OweMeter MCP server",
        "operationId": "callMcp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/McpJsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          }
        }
      }
    },
    "/api/export/expenses/{groupId}": {
      "get": {
        "tags": [
          "Protected"
        ],
        "summary": "Generate an expense export",
        "operationId": "expenseExport",
        "security": [
          {
            "ClerkOidc": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "ClerkBearer": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "pdf"
              ],
              "default": "csv"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requested export file",
            "content": {
              "text/csv": {},
              "application/pdf": {}
            }
          },
          "401": {
            "description": "Not authenticated"
          },
          "403": {
            "description": "Caller does not have access to the requested group"
          }
        }
      }
    },
    "/api/export/settlements/{groupId}": {
      "get": {
        "tags": [
          "Protected"
        ],
        "summary": "Generate a settlement export",
        "operationId": "settlementExport",
        "security": [
          {
            "ClerkOidc": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "ClerkBearer": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "pdf"
              ],
              "default": "csv"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requested export file",
            "content": {
              "text/csv": {},
              "application/pdf": {}
            }
          },
          "401": {
            "description": "Not authenticated"
          },
          "403": {
            "description": "Caller does not have access to the requested group"
          }
        }
      }
    },
    "/api/export/summary/{groupId}": {
      "get": {
        "tags": [
          "Protected"
        ],
        "summary": "Generate a summary export",
        "operationId": "summaryExport",
        "security": [
          {
            "ClerkOidc": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "ClerkBearer": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "pdf"
              ],
              "default": "csv"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requested export file",
            "content": {
              "text/csv": {},
              "application/pdf": {}
            }
          },
          "401": {
            "description": "Not authenticated"
          },
          "403": {
            "description": "Caller does not have access to the requested group"
          }
        }
      }
    },
    "/api/receipts/upload": {
      "post": {
        "tags": [
          "Protected"
        ],
        "summary": "Upload receipt files for an expense",
        "operationId": "uploadReceipt",
        "security": [
          {
            "ClerkOidc": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "ClerkBearer": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "expenseId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                },
                "required": [
                  "expenseId",
                  "files"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Receipt upload result"
          },
          "401": {
            "description": "Not authenticated"
          },
          "403": {
            "description": "Caller is not a member of the expense group"
          }
        }
      }
    },
    "/api/receipts/{receiptId}/{variant}": {
      "get": {
        "tags": [
          "Protected"
        ],
        "summary": "Fetch a receipt asset for an authorized group member",
        "operationId": "getReceiptVariant",
        "security": [
          {
            "ClerkOidc": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "ClerkBearer": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "parameters": [
          {
            "name": "receiptId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "variant",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "original",
                "display",
                "thumbnail"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Receipt asset bytes"
          },
          "401": {
            "description": "Not authenticated"
          },
          "403": {
            "description": "Caller is not authorized for the expense group"
          }
        }
      }
    },
    "/api/cron/run": {
      "get": {
        "tags": [
          "Operations"
        ],
        "summary": "Run scheduled reminder and recurring jobs",
        "operationId": "runCronJobs",
        "security": [
          {
            "CronSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "Job results"
          },
          "401": {
            "description": "Missing or invalid cron secret"
          },
          "409": {
            "description": "A job run is already in progress"
          }
        }
      }
    },
    "/api/group-reminders/open": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Reminder open tracking pixel",
        "operationId": "trackReminderOpen",
        "parameters": [
          {
            "name": "logId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transparent GIF pixel"
          }
        }
      }
    },
    "/api/group-reminders/click": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Reminder click redirect",
        "operationId": "trackReminderClick",
        "parameters": [
          {
            "name": "logId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the relevant settle-up flow"
          }
        }
      }
    }
  },
  "x-owemeter-discovery": {
    "apiCatalog": "https://owemeter.com/.well-known/api-catalog",
    "openapi": "https://owemeter.com/openapi.json",
    "docs": "https://owemeter.com/docs/api",
    "agentSkills": "https://owemeter.com/.well-known/agent-skills/index.json",
    "mcpServerCard": "https://owemeter.com/.well-known/mcp/server-card.json",
    "oauthAuthorizationServer": "https://owemeter.com/.well-known/oauth-authorization-server",
    "oauthProtectedResource": "https://owemeter.com/.well-known/oauth-protected-resource",
    "listedEndpoints": [
      {
        "method": "GET",
        "path": "/api/status",
        "access": "public",
        "description": "Health and discovery status for the public HTTP surface."
      },
      {
        "method": "GET",
        "path": "/api/blog",
        "access": "public",
        "description": "Paginated published blog feed with optional category and limit filters."
      },
      {
        "method": "GET",
        "path": "/api/mcp",
        "access": "public",
        "description": "Read-only MCP endpoint for discovery, help topics, and public blog search."
      },
      {
        "method": "GET",
        "path": "/api/export/expenses/{groupId}",
        "access": "session",
        "description": "Generate CSV or PDF expense exports for an authorized group member."
      },
      {
        "method": "GET",
        "path": "/api/export/settlements/{groupId}",
        "access": "session",
        "description": "Generate CSV or PDF settlement exports for an authorized group member."
      },
      {
        "method": "GET",
        "path": "/api/export/summary/{groupId}",
        "access": "session",
        "description": "Generate CSV or PDF summary exports for an authorized group member."
      },
      {
        "method": "POST",
        "path": "/api/receipts/upload",
        "access": "session",
        "description": "Upload receipt files for an expense when the caller is a group member."
      },
      {
        "method": "GET",
        "path": "/api/receipts/{receiptId}/{variant}",
        "access": "session",
        "description": "Fetch an original, display, or thumbnail receipt asset for authorized members."
      },
      {
        "method": "GET",
        "path": "/api/cron/run",
        "access": "secret",
        "description": "Trigger scheduled reminder and recurring-expense jobs with a cron secret."
      },
      {
        "method": "GET",
        "path": "/api/group-reminders/open",
        "access": "public",
        "description": "Pixel endpoint used for reminder email open tracking."
      },
      {
        "method": "GET",
        "path": "/api/group-reminders/click",
        "access": "public",
        "description": "Redirect endpoint used for reminder click tracking."
      }
    ]
  }
}