{
  "openapi": "3.1.0",
  "info": {
    "title": "The Boss API",
    "version": "0.3.0",
    "description": "Email-triage + CRM backend. Ingests forwarded emails via IMAP, stores messages/attachments/entities, and drives outbound actions on the user's Outlook via a 'director' command protocol. All endpoints except /health require bearer auth.\n\n### Writing to the database\nThree layers, pick the narrowest that fits:\n\n1. **Typed CRUD** (`/v1/opportunities`, `/v1/tasks`, `/v1/people`, `/v1/companies`, `/v1/entity-links`) \u2014 POST/PATCH/DELETE that work with user tokens. Handles embeddings + user-scope automatically. **Prefer these.**\n2. **Mutations** (`/v1/messages/:id/move|delete|read`, `/v1/director/send`) \u2014 act on real mailboxes via the director.\n3. **Raw SQL** (`/v1/admin/query`, admin bearer only) \u2014 escape hatch for anything the typed endpoints don't cover. Read the schema once via `GET /v1/admin/schema`, then write SQL directly.\n\n### Where is the database?\nThe Boss has **its own Postgres database** on the host, separate from SigmaDomain. Do not call SigmaDomain's `/query/raw` to read or write The Boss's data \u2014 use this API. The connection is encapsulated server-side.\n\n### Agent triage (added v0.3)\nPer-email triage runs through a headless Claude Code wrapper (claude-code-track, port 5350) by default, with Ollama as a fallback. The agent uses these endpoints to search existing entities, link or update them, complete or cancel tasks, and write back the category/summary/importance. Each call is logged in `agent_runs` for cost and behavior audit. Per-user prompts live at `prompts/users/<userId>/*.md` and are loaded after the universal `prompts/*.md` constitution on every triage call.",
    "contact": {
      "name": "The Boss",
      "url": "https://code.theboss.ai"
    }
  },
  "servers": [
    {
      "url": "https://code.theboss.ai",
      "description": "Production"
    },
    {
      "url": "http://127.0.0.1:5400",
      "description": "Local"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "x-authentication-notes": "Two bearer token types. Admin tokens start with `tb_` and have full access across all users. User tokens start with `tb_user_` and are scoped to that user's flow_user_email data only. To mint a user token: POST /v1/admin/users with an admin bearer. The Authorization header format is `Authorization: Bearer <token>`.",
  "tags": [
    {
      "name": "admin",
      "description": "User provisioning (admin token only)"
    },
    {
      "name": "messages",
      "description": "Ingested email messages"
    },
    {
      "name": "search",
      "description": "Semantic + full-text search"
    },
    {
      "name": "mutations",
      "description": "Actions on the source mailbox (move/flag/delete via the director)"
    },
    {
      "name": "director",
      "description": "Direct outbound command to director@theboss.ai"
    },
    {
      "name": "opportunities",
      "description": "Deals, projects, research topics"
    },
    {
      "name": "tasks",
      "description": "Action items"
    },
    {
      "name": "people",
      "description": "Contacts"
    },
    {
      "name": "companies",
      "description": "Organizations"
    },
    {
      "name": "attachments",
      "description": "File downloads from emails"
    },
    {
      "name": "ask",
      "description": "Briefing / Q&A over the corpus"
    },
    {
      "name": "entity-links",
      "description": "Polymorphic graph edges between people/companies/opportunities/tasks/messages"
    },
    {
      "name": "raw-sql",
      "description": "Admin-only escape hatch: direct Postgres access (SELECT/INSERT/UPDATE/DELETE/DDL). Use when no typed endpoint fits."
    },
    {
      "name": "agent",
      "description": "Triage agent: runs/usage history, manual triage trigger, cleanup sweeps"
    },
    {
      "name": "prompts",
      "description": "Editable markdown prompt files that drive the triage agent (constitution + per-user overrides)"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Liveness",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/v1/admin/users": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "List users",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Provision a new user (returns plaintext API key ONCE)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "display_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string"
                    },
                    "api_key": {
                      "type": "string",
                      "description": "Plaintext key \u2014 only returned once"
                    },
                    "api_key_prefix": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/admin/users/{id}/rotate": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Rotate a user's API key",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string"
                    },
                    "api_key": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/admin/users/{id}/deactivate": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Deactivate a user",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/messages": {
      "get": {
        "tags": [
          "messages"
        ],
        "summary": "List messages",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact category filter"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Substring match on subject or body_clean"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageSummary"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "get": {
        "tags": [
          "messages"
        ],
        "summary": "One message with attachment metadata",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "$ref": "#/components/schemas/Message"
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AttachmentMeta"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/search": {
      "post": {
        "tags": [
          "search"
        ],
        "summary": "Semantic search (pgvector + mxbai-embed-large, 1024-dim)",
        "description": "Embeds the text query server-side and runs cosine-distance search against stored embeddings. Callers pass plain text; The Boss handles embedding + vector math. By default searches `messages` only; pass `types` to include other entity tables. All results include a `score` field (1 - cosine distance; higher = more similar).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Plain-text query; server embeds it"
                  },
                  "limit": {
                    "type": "integer",
                    "default": 20,
                    "description": "Top-K per entity type"
                  },
                  "types": {
                    "type": "array",
                    "description": "Which entity tables to search. Default: messages only.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "messages",
                        "tasks",
                        "opportunities",
                        "people",
                        "companies"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `results` is always messages (back-compat). Other keys present when requested via `types`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageSearchHit"
                      }
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageSearchHit"
                      }
                    },
                    "tasks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Task row + score"
                      }
                    },
                    "opportunities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Opportunity row + score"
                      }
                    },
                    "people": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Person row + score"
                      }
                    },
                    "companies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Company row + score"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/move": {
      "post": {
        "tags": [
          "mutations"
        ],
        "summary": "Move email via director \u2192 user's Flow",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "target"
                ],
                "properties": {
                  "target": {
                    "type": "string",
                    "description": "Folder name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/messages/{id}/delete": {
      "post": {
        "tags": [
          "mutations"
        ],
        "summary": "Delete / trash email via director",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hard": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/messages/{id}/read": {
      "post": {
        "tags": [
          "mutations"
        ],
        "summary": "Mark read/unread via director",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "read": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/director/send": {
      "post": {
        "tags": [
          "director"
        ],
        "summary": "Directly trigger a director command email (general purpose)",
        "description": "Fires a command email from director@theboss.ai to the user. Use this to drive any of [MOVE], [FLAG], [DELETE], [SEND], [FORWARD] outbound actions on the user's Outlook.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DirectorCommand"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "smtpMessageId": {
                      "type": "string"
                    },
                    "command": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/opportunities": {
      "post": {
        "tags": [
          "opportunities"
        ],
        "summary": "Create an opportunity (auto-embeds name+description+notes)",
        "description": "User tokens: writes under the token's user. Admin tokens must pass `user_id` in the body to write on a user's behalf.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpportunityUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "opportunity": {
                      "$ref": "#/components/schemas/Opportunity"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "opportunities"
        ],
        "summary": "List opportunities",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "new_business",
                "existing_business",
                "strategic",
                "personal",
                "project",
                "learning"
              ]
            }
          },
          {
            "name": "stage",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "opportunities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Opportunity"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/opportunities/{id}": {
      "get": {
        "tags": [
          "opportunities"
        ],
        "summary": "One opportunity with its entity_links graph",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "opportunity": {
                      "$ref": "#/components/schemas/Opportunity"
                    },
                    "links": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EntityLink"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "opportunities"
        ],
        "summary": "Update an opportunity (partial; re-embeds if name/description/notes change)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpportunityUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "opportunity": {
                      "$ref": "#/components/schemas/Opportunity"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "opportunities"
        ],
        "summary": "Delete an opportunity",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/tasks": {
      "post": {
        "tags": [
          "tasks"
        ],
        "summary": "Create a task (auto-embeds title+description)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "tasks"
        ],
        "summary": "List tasks",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "in_progress",
                "done",
                "cancelled",
                "waiting"
              ]
            }
          },
          {
            "name": "priority",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "opportunity_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tasks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/tasks/{id}": {
      "patch": {
        "tags": [
          "tasks"
        ],
        "summary": "Update a task (title, description, priority, status, due_at)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "in_progress",
                      "done",
                      "cancelled",
                      "waiting"
                    ]
                  },
                  "due_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "tasks"
        ],
        "summary": "Delete a task",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/people": {
      "get": {
        "tags": [
          "people"
        ],
        "summary": "List people",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "people": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Person"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "people"
        ],
        "summary": "Create a person (auto-embeds name+display_name+email+notes)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "person": {
                      "$ref": "#/components/schemas/Person"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "email already exists for this user"
          }
        }
      }
    },
    "/v1/people/{id}": {
      "get": {
        "tags": [
          "people"
        ],
        "summary": "One person",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "tags": [
          "people"
        ],
        "summary": "Update a person",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "people"
        ],
        "summary": "Delete a person",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/companies": {
      "get": {
        "tags": [
          "companies"
        ],
        "summary": "List companies (sorted by mentions)",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "companies": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Company"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "companies"
        ],
        "summary": "Create a company (auto-embeds name+domain+industry+notes)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "company": {
                      "$ref": "#/components/schemas/Company"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "domain already exists for this user"
          }
        }
      }
    },
    "/v1/companies/{id}": {
      "get": {
        "tags": [
          "companies"
        ],
        "summary": "One company",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "tags": [
          "companies"
        ],
        "summary": "Update a company",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "companies"
        ],
        "summary": "Delete a company",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/entity-links": {
      "get": {
        "tags": [
          "entity-links"
        ],
        "summary": "List entity links (filter by any of source/target/relation)",
        "parameters": [
          {
            "name": "source_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "person",
                "company",
                "opportunity",
                "task",
                "message"
              ]
            }
          },
          {
            "name": "source_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "target_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "person",
                "company",
                "opportunity",
                "task",
                "message"
              ]
            }
          },
          {
            "name": "target_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "relation_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "links": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EntityLink"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "entity-links"
        ],
        "summary": "Create an entity link (upsert on unique key)",
        "description": "Unique on (user_id, source_type, source_id, target_type, target_id, relation_type). Conflicts update strength/confidence/role/notes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EntityLinkUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created or updated"
          }
        }
      }
    },
    "/v1/entity-links/{id}": {
      "delete": {
        "tags": [
          "entity-links"
        ],
        "summary": "Delete an entity link",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/admin/schema": {
      "get": {
        "tags": [
          "raw-sql"
        ],
        "summary": "Dump the public schema (columns + types for every table)",
        "description": "Admin bearer only. Call once to learn table shapes before writing raw SQL.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schema": {
                      "type": "string"
                    },
                    "tables": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "column": {
                              "type": "string"
                            },
                            "type": {
                              "type": "string"
                            },
                            "nullable": {
                              "type": "boolean"
                            },
                            "default": {
                              "type": "string",
                              "nullable": true
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/admin/query": {
      "post": {
        "tags": [
          "raw-sql"
        ],
        "summary": "Run arbitrary SQL (admin bearer only)",
        "description": "Escape hatch for anything the typed CRUD endpoints don't cover. Runs any SELECT / INSERT / UPDATE / DELETE / DDL statement against The Boss's Postgres database. Supports parameterized queries via `params`. Prefer typed endpoints when they fit \u2014 they handle embeddings and user-scope automatically.\n\nReturns `{ ok, rows, rowCount, fields }` on success (same shape as SigmaDomain's `/query/raw`), or `{ ok: false, error, code, detail }` on SQL error.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sql"
                ],
                "properties": {
                  "sql": {
                    "type": "string",
                    "description": "SQL statement. Use $1, $2, \u2026 for parameters."
                  },
                  "params": {
                    "type": "array",
                    "description": "Positional parameters, passed through to pg.",
                    "items": {}
                  }
                }
              },
              "examples": {
                "select": {
                  "value": {
                    "sql": "SELECT id, name FROM opportunities WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2",
                    "params": [
                      7,
                      10
                    ]
                  }
                },
                "insert": {
                  "value": {
                    "sql": "INSERT INTO tasks (user_id, title, status) VALUES ($1,$2,'open') RETURNING id",
                    "params": [
                      7,
                      "Follow up with PsiQuantum"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "rows": {
                      "type": "array",
                      "items": {}
                    },
                    "rowCount": {
                      "type": "integer"
                    },
                    "fields": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "dataTypeID": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "SQL error (returned as 400 so the AI can retry)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Admin token required"
          }
        }
      }
    },
    "/v1/attachments/{id}": {
      "get": {
        "tags": [
          "attachments"
        ],
        "summary": "Attachment metadata",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "attachment": {
                      "$ref": "#/components/schemas/AttachmentMeta"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/attachments/{id}/download": {
      "get": {
        "tags": [
          "attachments"
        ],
        "summary": "Download the attachment bytes",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Binary stream",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ask": {
      "post": {
        "tags": [
          "ask"
        ],
        "summary": "Qwen-backed briefing (local, free, fast)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "question"
                ],
                "properties": {
                  "question": {
                    "type": "string"
                  },
                  "limit": {
                    "type": "object",
                    "properties": {
                      "messages": {
                        "type": "integer"
                      },
                      "tasks": {
                        "type": "integer"
                      },
                      "opps": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/agent/messages/{id}": {
      "patch": {
        "tags": [
          "agent",
          "messages"
        ],
        "summary": "Agent writes back its triage verdict on a message",
        "description": "Updates a messages row's `category`, `summary`, `importance`. Re-embeds summary for semantic search. Called by the triage agent at the end of every email pass.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageMetaPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "$ref": "#/components/schemas/MessageSummary"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/agent/runs": {
      "get": {
        "tags": [
          "agent"
        ],
        "summary": "List agent runs (audit log)",
        "parameters": [
          {
            "name": "message_id",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Filter to runs for one message"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ok",
                "partial",
                "error",
                "skipped"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "runs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentRun"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/runs/{id}": {
      "get": {
        "tags": [
          "agent"
        ],
        "summary": "Get a single agent run with full raw_response",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "run": {
                      "$ref": "#/components/schemas/AgentRun"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/agent/usage": {
      "get": {
        "tags": [
          "agent"
        ],
        "summary": "Aggregate agent cost/volume by day",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 7
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily aggregates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "usage": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentUsageDay"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/triage/{messageId}": {
      "post": {
        "tags": [
          "agent"
        ],
        "summary": "Manually trigger triage on one message",
        "description": "Useful for testing prompt changes and reprocessing previously misclassified emails. Synchronous by default \u2014 returns the resulting agent_run row when finished. Pass `?async=true` to fire-and-forget and return immediately.",
        "parameters": [
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "async",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Fire-and-forget if true"
          }
        ],
        "responses": {
          "200": {
            "description": "Triage result (or {started:true} in async mode)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "started": {
                      "type": "boolean",
                      "description": "Returned in async mode"
                    },
                    "run": {
                      "$ref": "#/components/schemas/AgentRun"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Message not found"
          },
          "400": {
            "description": "Message has no registered user"
          }
        }
      }
    },
    "/v1/agent/cleanup": {
      "post": {
        "tags": [
          "agent"
        ],
        "summary": "Run a one-shot cleanup audit (duplicates, completable tasks, junk)",
        "description": "Agent reviews existing opportunities + open tasks for one user. Default scope=all; default dry_run=true (produces a markdown report without mutating). Pass `dry_run=false` to apply changes.",
        "parameters": [
          {
            "name": "user_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "opportunities",
                "tasks",
                "all"
              ],
              "default": "all"
            }
          },
          {
            "name": "dry_run",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cleanup result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CleanupResult"
                }
              }
            }
          }
        }
      }
    },
    "/v1/prompts": {
      "get": {
        "tags": [
          "prompts"
        ],
        "summary": "List the universal constitution files (admin only)",
        "description": "Returns the markdown files under `prompts/*.md` that get prepended to every triage call. Edit with PUT \u2014 changes are hot (next email reads the new file, no restart needed).",
        "responses": {
          "200": {
            "description": "Prompt list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dir": {
                      "type": "string"
                    },
                    "prompts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PromptFileSummary"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/prompts/{name}": {
      "get": {
        "tags": [
          "prompts"
        ],
        "summary": "Read one universal prompt file (admin only)",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]+\\.md$",
              "example": "boss.md"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File content",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromptFile"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "tags": [
          "prompts"
        ],
        "summary": "Replace one universal prompt file (admin only)",
        "description": "Hot \u2014 the next triage call reads the new content. Max 200kb.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]+\\.md$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string"
                  }
                },
                "required": [
                  "content"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Written",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromptFileSummary"
                }
              }
            }
          },
          "413": {
            "description": "Too large"
          }
        }
      }
    },
    "/v1/users/{userId}/prompts": {
      "get": {
        "tags": [
          "prompts"
        ],
        "summary": "List a user's per-user prompt overrides",
        "description": "Returns files under `prompts/users/<userId>/*.md`. These are appended to the universal constitution on every triage call for that user. Auth: admin OR the user themselves.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Prompt list (empty array if the user has no overrides yet)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dir": {
                      "type": "string"
                    },
                    "prompts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PromptFileSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not your user"
          }
        }
      }
    },
    "/v1/users/{userId}/prompts/{name}": {
      "get": {
        "tags": [
          "prompts"
        ],
        "summary": "Read a user's per-user prompt file",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]+\\.md$",
              "example": "profile.md"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File content",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromptFile"
                }
              }
            }
          },
          "403": {
            "description": "Not your user"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "tags": [
          "prompts"
        ],
        "summary": "Replace a user's per-user prompt file",
        "description": "Creates the user's prompt directory if it doesn't exist. Hot \u2014 the next triage call for that user reads the new content. Auth: admin OR the user themselves.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]+\\.md$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string"
                  }
                },
                "required": [
                  "content"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Written",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromptFileSummary"
                }
              }
            }
          },
          "403": {
            "description": "Not your user"
          },
          "413": {
            "description": "Too large"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque",
        "description": "Two token types:\n\n1. **Admin tokens** (`tb_` prefix): full cross-user access. Set via env `ADMIN_BEARER_TOKEN`. Required for `/v1/admin/*` paths.\n2. **User tokens** (`tb_user_` prefix): scoped to one user's flow_user_email. Mint via `POST /v1/admin/users`.\n\nSend as header: `Authorization: Bearer <token>`. All paths except `/health` and `/openapi.json` require authentication."
      }
    },
    "responses": {
      "Conflict": {
        "description": "Conflict",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "api_key_prefix": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MessageSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "envelope_from": {
            "type": "string"
          },
          "envelope_subject": {
            "type": "string"
          },
          "envelope_date": {
            "type": "string",
            "format": "date-time"
          },
          "category": {
            "type": "string"
          },
          "importance": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          },
          "summary": {
            "type": "string"
          },
          "flow_user_email": {
            "type": "string"
          },
          "is_flow_wrapped": {
            "type": "boolean"
          }
        }
      },
      "Message": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MessageSummary"
          },
          {
            "type": "object",
            "properties": {
              "message_id": {
                "type": "string"
              },
              "envelope_to": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "envelope_cc": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "body_text": {
                "type": "string"
              },
              "body_clean": {
                "type": "string"
              },
              "action_items": {
                "type": "array"
              },
              "entities": {
                "type": "object"
              },
              "original_from": {
                "type": "string"
              },
              "original_subject": {
                "type": "string"
              },
              "outlook_message_id": {
                "type": "string"
              },
              "outlook_conversation_id": {
                "type": "string"
              }
            }
          }
        ]
      },
      "MessageSearchHit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "envelope_from": {
            "type": "string"
          },
          "envelope_subject": {
            "type": "string"
          },
          "envelope_date": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "score": {
            "type": "number",
            "description": "1 - cosine distance; higher is more similar"
          }
        }
      },
      "DirectorCommand": {
        "type": "object",
        "required": [
          "to",
          "command"
        ],
        "properties": {
          "to": {
            "type": "string",
            "format": "email",
            "description": "Recipient (usually the Flow user)"
          },
          "command": {
            "type": "string",
            "enum": [
              "[MOVE]",
              "[FLAG]",
              "[DELETE]",
              "[SEND]",
              "[FORWARD]"
            ]
          },
          "emailId": {
            "type": "string",
            "description": "Original email's RFC Message-ID (required for MOVE/FLAG/DELETE/FORWARD)"
          },
          "target": {
            "type": "string",
            "description": "Folder for [MOVE] or recipient for [FORWARD]"
          },
          "folderId": {
            "type": "string"
          },
          "siteId": {
            "type": "string"
          },
          "driveId": {
            "type": "string"
          },
          "sourceMessageId": {
            "type": "integer",
            "description": "The Boss messages.id for audit-log linkage"
          },
          "conversationId": {
            "type": "string"
          },
          "message": {
            "type": "object",
            "properties": {
              "subject": {
                "type": "string"
              },
              "content": {
                "type": "string"
              },
              "cc": {
                "type": "string"
              },
              "bcc": {
                "type": "string"
              },
              "importance": {
                "type": "string",
                "enum": [
                  "low",
                  "normal",
                  "high"
                ],
                "default": "normal"
              },
              "attachments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "contentType": {
                      "type": "string"
                    },
                    "contentBytes": {
                      "type": "string",
                      "description": "base64-encoded file content"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Opportunity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "type": {
            "type": "string",
            "enum": [
              "new_business",
              "existing_business",
              "strategic",
              "personal",
              "project",
              "learning"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "stage": {
            "type": "string"
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "value_cents": {
            "type": "integer"
          },
          "due_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_touched_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "in_progress",
              "done",
              "cancelled",
              "waiting"
            ]
          },
          "due_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "source_message_id": {
            "type": "integer"
          },
          "opportunity_id": {
            "type": "integer"
          },
          "assignee_person_id": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Person": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "company_id": {
            "type": "integer"
          },
          "company_name": {
            "type": "string"
          }
        }
      },
      "Company": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "mentions": {
            "type": "integer"
          },
          "employees": {
            "type": "integer"
          }
        }
      },
      "AttachmentMeta": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "message_id": {
            "type": "integer"
          },
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "storage_path": {
            "type": "string"
          },
          "extracted_text": {
            "type": "string"
          }
        }
      },
      "EntityLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "source_type": {
            "type": "string",
            "enum": [
              "person",
              "company",
              "opportunity",
              "task",
              "message"
            ]
          },
          "source_id": {
            "type": "integer"
          },
          "target_type": {
            "type": "string"
          },
          "target_id": {
            "type": "integer"
          },
          "relation_type": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "confidence": {
            "type": "number"
          },
          "strength": {
            "type": "number"
          },
          "start_date": {
            "type": "string",
            "format": "date"
          },
          "end_date": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "OpportunityUpsert": {
        "type": "object",
        "description": "Body for POST (create) and PATCH (partial update). For PATCH, only include the fields you want changed; omitted fields are preserved. Embeddings are recomputed when name, description, or notes changes.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "new_business",
              "existing_business",
              "strategic",
              "personal",
              "project",
              "learning"
            ],
            "description": "Required on create"
          },
          "name": {
            "type": "string",
            "description": "Required on create"
          },
          "description": {
            "type": "string"
          },
          "stage": {
            "type": "string",
            "description": "Free-form: lead | active | won | lost | paused | closed"
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "value_cents": {
            "type": "integer"
          },
          "due_at": {
            "type": "string",
            "format": "date-time"
          },
          "notes": {
            "type": "string"
          },
          "metadata": {
            "type": "object"
          },
          "user_id": {
            "type": "integer",
            "description": "Admin-only: write on behalf of this user. Ignored for user tokens."
          }
        }
      },
      "TaskUpsert": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Required on create"
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "in_progress",
              "done",
              "cancelled",
              "waiting"
            ]
          },
          "due_at": {
            "type": "string",
            "format": "date-time"
          },
          "source_message_id": {
            "type": "integer"
          },
          "opportunity_id": {
            "type": "integer"
          },
          "assignee_person_id": {
            "type": "integer"
          },
          "metadata": {
            "type": "object"
          },
          "user_id": {
            "type": "integer",
            "description": "Admin-only"
          }
        }
      },
      "PersonUpsert": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "company_id": {
            "type": "integer"
          },
          "notes": {
            "type": "string"
          },
          "metadata": {
            "type": "object"
          },
          "user_id": {
            "type": "integer",
            "description": "Admin-only"
          }
        }
      },
      "CompanyUpsert": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Required on create"
          },
          "domain": {
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "metadata": {
            "type": "object"
          },
          "user_id": {
            "type": "integer",
            "description": "Admin-only"
          }
        }
      },
      "EntityLinkUpsert": {
        "type": "object",
        "required": [
          "source_type",
          "source_id",
          "target_type",
          "target_id",
          "relation_type"
        ],
        "properties": {
          "source_type": {
            "type": "string",
            "enum": [
              "person",
              "company",
              "opportunity",
              "task",
              "message"
            ]
          },
          "source_id": {
            "type": "integer"
          },
          "target_type": {
            "type": "string",
            "enum": [
              "person",
              "company",
              "opportunity",
              "task",
              "message"
            ]
          },
          "target_id": {
            "type": "integer"
          },
          "relation_type": {
            "type": "string",
            "description": "Free-form, e.g. broker_for, tenant_of, employs, assigned_to, mentioned_in, data_for, referred_by"
          },
          "role": {
            "type": "string"
          },
          "strength": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1.0
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1.0
          },
          "start_date": {
            "type": "string",
            "format": "date"
          },
          "end_date": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": "string"
          },
          "source_message_id": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer",
            "description": "Admin-only"
          }
        }
      },
      "AgentRun": {
        "type": "object",
        "description": "A single triage or cleanup call audit row. Captures cost, tokens, turns, the agent's final actions, and the prompt version used.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "message_id": {
            "type": "integer",
            "nullable": true,
            "description": "NULL for cleanup runs (which are not message-scoped)"
          },
          "user_id": {
            "type": "integer"
          },
          "model": {
            "type": "string",
            "example": "haiku"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "partial",
              "error",
              "skipped"
            ]
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          },
          "actions": {
            "type": "array",
            "description": "The agent's final action list e.g. [{\"kind\":\"link_opp\",\"id\":42},{\"kind\":\"complete_task\",\"id\":17,\"reason\":\"...\"}]"
          },
          "cost_usd": {
            "type": "number",
            "description": "Per-call cost in USD"
          },
          "num_turns": {
            "type": "integer"
          },
          "duration_ms": {
            "type": "integer"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "cache_read_tokens": {
            "type": "integer"
          },
          "cache_write_tokens": {
            "type": "integer"
          },
          "prompt_version": {
            "type": "string",
            "description": "Hash of the constitution at run time; lets you diff quality across prompt edits"
          },
          "raw_response": {
            "type": "string",
            "description": "Full final-message text from the agent, truncated to 32kb",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AgentUsageDay": {
        "type": "object",
        "properties": {
          "day": {
            "type": "string",
            "format": "date-time"
          },
          "runs": {
            "type": "integer"
          },
          "ok": {
            "type": "integer"
          },
          "errors": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "cost_usd": {
            "type": "number"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "avg_ms": {
            "type": "integer"
          }
        }
      },
      "MessageMetaPatch": {
        "type": "object",
        "description": "The triage agent's verdict \u2014 overwrites category, summary, importance on a messages row",
        "properties": {
          "category": {
            "type": "string",
            "description": "See categories.md prompt file for valid values"
          },
          "summary": {
            "type": "string",
            "description": "3-sentence summary; re-embeds for semantic search"
          },
          "importance": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          }
        }
      },
      "PromptFileSummary": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "boss.md"
          },
          "size": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PromptFile": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CleanupResult": {
        "type": "object",
        "description": "Result of POST /v1/agent/cleanup. The `report` field is the agent's full markdown audit.",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "dry_run": {
            "type": "boolean"
          },
          "scope": {
            "type": "string",
            "enum": [
              "opportunities",
              "tasks",
              "all"
            ]
          },
          "runId": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "partial",
              "error"
            ]
          },
          "report": {
            "type": "string",
            "description": "Markdown report from the agent \u2014 duplicate clusters, completions, cancellations, junk identified"
          },
          "costUsd": {
            "type": "number"
          },
          "durationMs": {
            "type": "integer"
          },
          "numTurns": {
            "type": "integer"
          },
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}