{
  "openapi": "3.1.0",
  "info": {
    "title": "Tonos API",
    "version": "1.0.0",
    "description": "Voice profile API. Build a persistent voice profile from writing samples, then draft or rewrite messages that sound like you, not like AI.",
    "contact": {
      "email": "max@tonos.fyi",
      "url": "https://tonos.fyi"
    }
  },
  "servers": [
    {
      "url": "https://tonos.fyi",
      "description": "Production"
    },
    {
      "url": "http://localhost:3003",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key with `tnos_live_` prefix. Create one at tonos.fyi/settings or via POST /keys."
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "AUTH_REQUIRED",
              "AUTH_INVALID",
              "AUTH_FAILED",
              "CONFLICT",
              "RATE_LIMITED",
              "BUDGET_EXCEEDED",
              "CREDITS_INSUFFICIENT",
              "MODEL_NOT_AVAILABLE",
              "VALIDATION_ERROR",
              "NOT_FOUND",
              "INTERNAL_ERROR",
              "EXTRACTION_ERROR",
              "GENERATION_ERROR"
            ]
          },
          "message": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          }
        }
      },
      "WritingSample": {
        "type": "object",
        "required": ["text"],
        "properties": {
          "text": {
            "type": "string",
            "description": "The writing sample text"
          },
          "platform": {
            "type": "string",
            "description": "Source platform: imessage, slack, whatsapp, linkedin, email, etc."
          },
          "sample_date": {
            "type": "string",
            "description": "Date the sample was written (ISO 8601)"
          },
          "context": {
            "type": "string",
            "description": "Optional context about the sample"
          }
        }
      },
      "VoiceProfile": {
        "type": "object",
        "description": "Structured voice profile with dimensions, vocabulary patterns, and summary. Each dimension has a `value` (0-10 scale or string) and `confidence` (0-1)."
      },
      "ProfileCreateResponse": {
        "type": "object",
        "required": ["profile_id", "voice", "confidence", "confidence_label", "tokens_used"],
        "properties": {
          "profile_id": { "type": "string" },
          "voice": { "$ref": "#/components/schemas/VoiceProfile" },
          "confidence": { "type": "number", "description": "Overall confidence score (0-1)" },
          "confidence_label": { "type": "string", "enum": ["high", "medium", "low"] },
          "tokens_used": { "type": "integer" }
        }
      },
      "ProfileGetResponse": {
        "type": "object",
        "required": ["profile_id", "voice", "confidence", "confidence_label", "version", "extracted_at"],
        "properties": {
          "profile_id": { "type": "string" },
          "voice": { "$ref": "#/components/schemas/VoiceProfile" },
          "confidence": { "type": "number" },
          "confidence_label": { "type": "string", "enum": ["high", "medium", "low"] },
          "version": { "type": "integer" },
          "extracted_at": { "type": "string", "format": "date-time" }
        }
      },
      "GenerationResult": {
        "type": "object",
        "required": ["text", "profile_id", "tokens_used"],
        "properties": {
          "text": { "type": "string", "description": "The generated or rewritten text" },
          "profile_id": { "type": "string" },
          "tokens_used": { "type": "integer" }
        }
      },
      "ApiKeyInfo": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "key_prefix": { "type": "string" },
          "label": { "type": "string" },
          "last_used_at": { "type": "string", "format": "date-time", "nullable": true },
          "revoked": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "UsageSummary": {
        "type": "object",
        "properties": {
          "total_calls": { "type": "integer" },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "endpoint": { "type": "string" },
                "call_count": { "type": "integer" }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/keys": {
      "post": {
        "summary": "Create API key",
        "description": "Create a new API key for authenticating API requests. The plaintext key is only returned once -- store it securely.",
        "tags": ["Keys"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["label"],
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Human-readable label for this key"
                  }
                }
              },
              "example": {
                "label": "production-server"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "plaintext": { "type": "string", "description": "Full API key (tnos_live_...). Only shown once." },
                    "prefix": { "type": "string", "description": "Key prefix for identification" }
                  }
                },
                "example": {
                  "id": "key_abc123",
                  "plaintext": "tnos_live_abc123def456...",
                  "prefix": "tnos_live_abc1"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List API keys",
        "description": "List all API keys for your account. Keys are returned without the plaintext value.",
        "tags": ["Keys"],
        "responses": {
          "200": {
            "description": "Key list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/ApiKeyInfo" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/keys/{id}": {
      "delete": {
        "summary": "Revoke API key",
        "description": "Permanently revoke an API key. The key will immediately stop working.",
        "tags": ["Keys"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": { "type": "boolean", "const": true }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Key not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/keys/{id}/usage": {
      "get": {
        "summary": "Key usage stats",
        "description": "Get usage summary for a specific API key.",
        "tags": ["Keys"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UsageSummary" }
              }
            }
          },
          "404": {
            "description": "Key not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/profile": {
      "post": {
        "summary": "Create voice profile",
        "description": "Create a new voice profile from writing samples. Requires at least 3 samples. The profile is extracted via two-pass AI analysis and a structured voice profile is returned.",
        "tags": ["Profiles"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["samples"],
                "properties": {
                  "samples": {
                    "type": "array",
                    "minItems": 3,
                    "items": { "$ref": "#/components/schemas/WritingSample" }
                  },
                  "platform": {
                    "type": "string",
                    "description": "Default platform context for all samples"
                  }
                }
              },
              "example": {
                "samples": [
                  { "text": "hey just checking in on that proposal, lmk if you need anything" },
                  { "text": "sounds good, i can hop on a call tomorrow if easier" },
                  { "text": "perfect, will send over the updated deck by end of day. no rush on your end" }
                ],
                "platform": "slack"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Profile created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProfileCreateResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error (e.g., fewer than 3 samples)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "502": {
            "description": "Voice extraction failed (upstream LLM error)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/profile/{id}": {
      "get": {
        "summary": "Get voice profile",
        "description": "Retrieve the active version of a voice profile.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Profile data",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProfileGetResponse" }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete voice profile",
        "description": "Delete a voice profile and all associated data (versions, samples). This is permanent.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Profile deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": { "type": "boolean", "const": true }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/profile/{id}/samples": {
      "post": {
        "summary": "Add samples to profile",
        "description": "Add new writing samples to an existing profile and re-extract the voice profile. Requires at least 3 total samples after adding.",
        "tags": ["Profiles"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["samples"],
                "properties": {
                  "samples": {
                    "type": "array",
                    "minItems": 1,
                    "items": { "$ref": "#/components/schemas/WritingSample" }
                  },
                  "platform": {
                    "type": "string",
                    "description": "Default platform context for new samples"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile re-extracted with new samples",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProfileCreateResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/generate/draft": {
      "post": {
        "summary": "Draft a new message",
        "description": "Generate a new message in the voice of the specified profile. Supports streaming via SSE (default) or non-streaming with `?stream=false`.",
        "tags": ["Generation"],
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["true", "false"],
              "default": "true"
            },
            "description": "Set to 'false' to disable SSE streaming and return the full response as JSON."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["profile_id", "recipient", "purpose", "key_points"],
                "properties": {
                  "profile_id": { "type": "string", "description": "Voice profile ID to use" },
                  "recipient": { "type": "string", "maxLength": 500, "description": "Who the message is for" },
                  "purpose": { "type": "string", "maxLength": 5000, "description": "What the message should accomplish" },
                  "key_points": {
                    "type": "array",
                    "items": { "type": "string", "maxLength": 5000 },
                    "minItems": 1,
                    "maxItems": 10,
                    "description": "Key points to include in the message"
                  },
                  "platform": { "type": "string", "description": "Platform voice mode: imessage, slack, linkedin, email, etc." }
                }
              },
              "example": {
                "profile_id": "prof_abc123",
                "recipient": "Sarah",
                "purpose": "Follow up on the partnership proposal",
                "key_points": ["Check if she reviewed the proposal", "Offer to hop on a call"],
                "platform": "email"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated message (non-streaming) or SSE event stream (streaming)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GenerationResult" }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream: `data:` events with text chunks, `event: metadata` with profile_id and tokens_used, `data: [DONE]` on completion."
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "502": {
            "description": "Generation failed (upstream LLM error)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    },
    "/generate/rewrite": {
      "post": {
        "summary": "Rewrite text in voice",
        "description": "Rewrite source text to match the voice of the specified profile. Preserves meaning, transforms style. Supports SSE streaming (default) or `?stream=false`.",
        "tags": ["Generation"],
        "parameters": [
          {
            "name": "stream",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["true", "false"],
              "default": "true"
            },
            "description": "Set to 'false' to disable SSE streaming."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["profile_id", "source_text"],
                "properties": {
                  "profile_id": { "type": "string", "description": "Voice profile ID to use" },
                  "source_text": { "type": "string", "maxLength": 10000, "description": "Text to rewrite in the profile's voice" },
                  "platform": { "type": "string", "description": "Platform voice mode: imessage, slack, linkedin, email, etc." }
                }
              },
              "example": {
                "profile_id": "prof_abc123",
                "source_text": "Dear Sarah, I hope this email finds you well. I wanted to follow up regarding our previous conversation about the partnership opportunity.",
                "platform": "email"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rewritten text (non-streaming) or SSE event stream (streaming)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GenerationResult" }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream: `data:` events with text chunks, `event: metadata` with profile_id and tokens_used, `data: [DONE]` on completion."
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "404": {
            "description": "Profile not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "502": {
            "description": "Generation failed (upstream LLM error)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    { "name": "Keys", "description": "API key management" },
    { "name": "Profiles", "description": "Voice profile CRUD and extraction" },
    { "name": "Generation", "description": "Draft and rewrite messages in voice" }
  ]
}
