{"openapi":"3.1.0","info":{"title":"LegacyCore Public API","version":"1.1.0","description":"Authenticated REST API for LegacyCore. Use a scoped LegacyCore API key (prefix `lc_`) in the Authorization header. Rate limit defaults to 1000 requests/hour per key. \n\n**Access scope**: by default, every key is owner-scoped — it can only see and act on records owned by the user who created the key. Keys granted the `api:full_access` scope (typically service keys such as Hermes Conservation) bypass this filter and can read/write across all agents. Every response includes a `scope` field (`owner` or `full_access`) so integrators can confirm which mode they are in.","contact":{"name":"LEGACYCORE LLC","url":"https://www.legacycore.io"}},"servers":[{"url":"https://www.legacycore.io","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Profile","description":"Authenticated user profile"},{"name":"Applications","description":"Insurance applications. Owner-scoped by default; keys with `api:full_access` see all agents."},{"name":"Conservation","description":"Retention / conservation activity log written by external AI agents (e.g. Hermes)"},{"name":"Notifications","description":"User-targeted in-app notifications and web-push fan-out"},{"name":"Keys","description":"API key management — list, create, rotate, and revoke keys within the caller's organization"}],"paths":{"/api/v1/profile":{"get":{"tags":["Profile"],"summary":"Get the authenticated user profile","security":[{"bearerAuth":["read:profile"]}],"responses":{"200":{"description":"Profile retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Profile not found"}}},"put":{"tags":["Profile"],"summary":"Update the authenticated user profile","security":[{"bearerAuth":["write:profile"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","maxLength":50},"last_name":{"type":"string","maxLength":50},"avatar_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Profile updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/applications":{"get":{"tags":["Applications"],"summary":"List applications","description":"Owner-scoped by default: keys see only applications where `agent_id` equals the key holder. Keys with the `api:full_access` scope see every agent's applications and may additionally narrow by `agent_id`. The response `scope` field (`owner` | `full_access`) reflects which mode was applied.","security":[{"bearerAuth":["read:applications"]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"status","in":"query","description":"Exact match on the application status column","schema":{"type":"string"}},{"name":"carrier","in":"query","description":"Partial (ILIKE) match on the carrier text column. This is the canonical filter name.","schema":{"type":"string"}},{"name":"carrier_id","in":"query","deprecated":true,"description":"Deprecated legacy alias for `carrier`. The underlying column is a text name, not a UUID — use `carrier` instead. `carrier_id` is still honored for backwards compatibility.","schema":{"type":"string"}},{"name":"agent_id","in":"query","description":"Filter by owning agent. Honored only for `api:full_access` keys; ignored for owner-scoped keys (which can only see their own records anyway).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of applications with pagination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Applications"],"summary":"Create a new application","security":[{"bearerAuth":["write:applications"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationRequest"}}}},"responses":{"201":{"description":"Application created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/applications/{id}":{"get":{"tags":["Applications"],"summary":"Fetch a single application by id","description":"Owner-scoped by default. Keys with `api:full_access` can fetch any application by id regardless of owning agent.","security":[{"bearerAuth":["read:applications"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Application found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationResponse"}}}},"400":{"description":"Invalid application id"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Application not found (or not owned by this key, when owner-scoped)"}}},"patch":{"tags":["Applications"],"summary":"Update an application (partial)","description":"Partial update for an application. When `recording_url` changes, triggers the post-submit fan-out (transcribe → policy-summary PDF → in-house drafts → Hermes Conservation CS notification). Owner-scoped by default; `api:full_access` keys can patch any application.","operationId":"patchApplicationById","security":[{"bearerAuth":["write:applications"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"recording_url":{"type":"string","nullable":true},"ai_transcript":{"type":"string","nullable":true},"ai_summary":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"payment_notes":{"type":"string","nullable":true},"status":{"type":"string"},"policy_number":{"type":"string","nullable":true},"monthly_premium":{"type":"number"},"coverage_amount":{"type":"number"},"carrier":{"type":"string"},"product":{"type":"string"},"effective_policy_date":{"type":"string","format":"date"},"primary_beneficiary":{"type":"string","nullable":true},"relationship_to_insured":{"type":"string","nullable":true},"policy_payment_cycle":{"type":"string","nullable":true},"tobacco_nicotine_use":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Application updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Application"},"auth_type":{"type":"string","enum":["api_key","session"]},"fanout_triggered":{"type":"boolean"}}}}}},"400":{"description":"Invalid id, JSON body, or no patchable fields provided"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden — not the application owner"},"404":{"description":"Application not found"},"500":{"description":"Internal server error"}}}},"/api/v1/applications/{id}/policy-summary":{"get":{"tags":["Applications"],"summary":"Fetch the generated policy summary PDF for an application","description":"Returns a fresh 1-hour signed URL for the personalized policy summary PDF tied to this application. Summaries are generated automatically on application submit, bulk import, and can be regenerated on demand. Used by Hermes Conservation to attach the correct summary to retention outreach. Owner-scoped by default; `api:full_access` keys can fetch any application.","security":[{"bearerAuth":["read:applications"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Policy summary found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicySummaryResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Summary not found (or not owned by this key, when owner-scoped)"}}}},"/api/v1/clients/policy-summaries":{"get":{"tags":["Conservation"],"summary":"List all policy summaries for a client / household","description":"Returns every stored policy summary matching a given phone, email, or household_id so Hermes Conservation can attach the right set during outreach — including every policy a single client holds AND every policy held by other members of the same family/household. Exactly one of `phone`, `email`, or `household_id` is required. Owner-scoped keys see only their own clients; `api:full_access` spans the whole book.","security":[{"bearerAuth":["read:applications"]}],"parameters":[{"name":"phone","in":"query","required":false,"schema":{"type":"string","description":"Free-form phone; normalized to last 10 digits server-side."}},{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email"}},{"name":"household_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Matching summaries (may be empty)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicySummaryListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/conservation/events":{"post":{"tags":["Conservation"],"summary":"Record a conservation / retention activity event","description":"Persists an outreach attempt or outcome from an external AI agent (e.g. Hermes Conservation CS) into the LegacyCore events table. Owner-scoped keys can only log against applications they own; `api:full_access` keys can log against any application and the event is recorded under the true owning agent so the audit trail stays honest (the key holder is preserved in `metadata.written_by_key_holder`).","security":[{"bearerAuth":["write:conversations"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConservationEventRequest"}}}},"responses":{"201":{"description":"Event recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConservationEventResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Application not found (or not owned by this key, when owner-scoped)"}}}},"/api/v1/notifications":{"post":{"tags":["Notifications"],"summary":"Create a notification for a LegacyCore user","description":"External AI agents (Hermes Conservation CS, Ops, etc.) call this to push a notification into a LegacyCore user's bell + web-push. Requires the write:notifications scope on the API key.","security":[{"bearerAuth":["write:notifications"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id","title"],"properties":{"user_id":{"type":"string","format":"uuid","description":"Target LegacyCore user (e.g. agent_applications.agent_id)"},"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"type":{"type":"string","enum":["success","info","warning","error"],"default":"info"},"category":{"type":"string","enum":["general","application","commission","system","reminder"],"default":"general"},"action_url":{"type":"string"},"action_label":{"type":"string"},"metadata":{"type":"object","additionalProperties":true},"push":{"type":"boolean","default":true,"description":"When false, never sends web push regardless of user preferences."}}}}}},"responses":{"201":{"description":"Notification created"},"400":{"description":"Invalid body"},"401":{"description":"Authentication required or scope missing"},"500":{"description":"Insert failed"}}}},"/api/v1/keys":{"get":{"tags":["Keys"],"operationId":"listApiKeys","summary":"List API keys for the caller's organization","description":"Returns all non-secret API key metadata rows for the organization that owns the calling key. The plaintext key value is never returned by this endpoint — it is only available at creation time. Requires API-key authentication (Bearer lc_...); session cookies are rejected.","security":[{"bearerAuth":["read:keys"]}],"responses":{"200":{"description":"Array of key metadata","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyMetadata"}}}}},"400":{"description":"Unable to determine organization for calling key"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Keys"],"operationId":"createApiKey","summary":"Create a new API key","description":"Creates a new API key for the same organization as the calling key. The plaintext key (`key` field) is returned only once in this response — store it immediately. Requires API-key authentication (Bearer lc_...); session cookies are rejected.","security":[{"bearerAuth":["write:keys"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}}},"responses":{"201":{"description":"New key created — plaintext key included (shown once only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"description":"Key creation failed"}}}},"/api/v1/keys/{id}":{"delete":{"tags":["Keys"],"operationId":"revokeApiKey","summary":"Revoke (hard-delete) an API key","description":"Permanently deletes the target API key. The key must belong to the same organization as the caller. This is a hard delete — the key cannot be recovered. Requires API-key authentication (Bearer lc_...); session cookies are rejected.","security":[{"bearerAuth":["write:keys"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of the API key to revoke"}],"responses":{"204":{"description":"Key revoked — no content"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Key not found or belongs to a different organization"},"500":{"description":"Delete failed"}}}},"/api/v1/keys/{id}/rotate":{"post":{"tags":["Keys"],"operationId":"rotateApiKey","summary":"Rotate an API key","description":"Revokes the target key and creates a replacement with the same label and scopes. The plaintext replacement key (`key` field) is returned only once — store it immediately. The key must belong to the same organization as the calling key. Requires API-key authentication (Bearer lc_...); session cookies are rejected.","security":[{"bearerAuth":["write:keys"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of the API key to rotate"}],"responses":{"201":{"description":"New key created — plaintext key included (shown once only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Key not found or belongs to a different organization"},"500":{"description":"Revocation or replacement failed"}}}},"/api/v1/whoami":{"get":{"tags":["Profile"],"operationId":"whoami","summary":"Get info about the authenticated API key","description":"Returns metadata about the calling API key: id, prefix, scopes, rate limits, expiry, and label. Used by the LegacyCore CLI on first auth to verify the key is valid and to stamp `cli_paired_at`. Only API-key auth (`Bearer lc_...`) is accepted — session cookies are rejected.","security":[{"bearerAuth":["read:profile"]}],"responses":{"200":{"description":"API key info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoamiResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Key not found in database"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"LegacyCore API Key (prefix lc_) or JWT","description":"Pass the full API key as `Authorization: Bearer <key>`. Scopes are enforced per endpoint. Known scopes: `read:profile`, `write:profile`, `read:applications`, `write:applications`, `write:conversations`, `write:notifications`, `api:full_access`."}},"responses":{"Unauthorized":{"description":"Missing, invalid, expired, or insufficiently scoped credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Validation error in request body or query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Pagination":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"}},"required":["total","limit","offset","has_more"]},"Profile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"full_name":{"type":"string","nullable":true},"role":{"type":"string","enum":["admin","manager","agent"]},"organization_id":{"type":"string","format":"uuid","nullable":true},"avatar_url":{"type":"string","format":"uri","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ProfileResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Profile"},"auth_type":{"type":"string","enum":["api_key","session"]}}},"Application":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"proposed_insured":{"type":"string"},"client_email":{"type":"string","format":"email","nullable":true},"client_phone_number":{"type":"string","nullable":true},"product":{"type":"string"},"carrier":{"type":"string"},"monthly_premium":{"type":"number"},"coverage_amount":{"type":"number","nullable":true},"status":{"type":"string","nullable":true,"enum":["Pending","Underwriting","Approved","Issued","Active","Free Look","Incomplete","Cancelled","Lapsed","Carrier Declined","Declined","DNQ"],"description":"Application lifecycle status. Outcomes (Cancelled, Lapsed, Carrier Declined, Declined, DNQ) are terminal. Enforced by DB CHECK constraint."},"policy_health":{"type":"string","nullable":true,"enum":["Active","Pending First Payment","Payment Issues","Needs Attention","Cancelled"],"description":"Coarse policy health. 'Needs Attention' is grandfathered for back-compat; consumers should derive attention from the rules in src/lib/applications/attention-signal.ts (or the agent_applications_with_attention SQL view) rather than relying on this column."},"paid_status":{"type":"string","nullable":true,"enum":["Paid","Unpaid","Partial","Pending","Cancelled","Return Draft","Not Payable - License"]},"policy_number":{"type":"string","nullable":true},"recording_url":{"type":"string","format":"uri","nullable":true},"ai_summary":{"type":"string","nullable":true},"needs_attention_reason":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ApplicationListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Application"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"auth_type":{"type":"string","enum":["api_key","session"]}}},"ApplicationResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Application"},"auth_type":{"type":"string","enum":["api_key","session"]}}},"CreateApplicationRequest":{"type":"object","description":"Request field names differ from the persisted column names. The API accepts `client_name`, `client_email`, `client_phone`, `product_type`, `carrier_id`, `premium_amount` and maps them internally to `proposed_insured`, `client_email`, `client_phone_number`, `product`, `carrier` (resolved from the carrier record), and `monthly_premium` respectively. `carrier_id` here IS a UUID — it is resolved against the carriers table, unlike the GET /applications `carrier_id` filter which is a legacy text alias.","required":["client_name","product_type","carrier_id","premium_amount"],"properties":{"client_name":{"type":"string","description":"Persisted as agent_applications.proposed_insured"},"client_email":{"type":"string","format":"email"},"client_phone":{"type":"string","description":"Persisted as agent_applications.client_phone_number"},"product_type":{"type":"string","description":"Persisted as agent_applications.product"},"carrier_id":{"type":"string","format":"uuid","description":"UUID of a row in the carriers table; resolved name is stored on agent_applications.carrier"},"premium_amount":{"type":"number","exclusiveMinimum":0,"description":"Persisted as agent_applications.monthly_premium"},"recording_url":{"type":"string","format":"uri"}}},"ConservationEventRequest":{"type":"object","required":["application_id","channel","outcome"],"properties":{"application_id":{"type":"string","format":"uuid"},"channel":{"type":"string","enum":["sms","email","call","voicemail","letter","chat","other"]},"outcome":{"type":"string","enum":["attempted","reached","no_answer","left_voicemail","opted_out","reactivated","unreachable","scheduled_callback","note_only"]},"reason":{"type":"string","maxLength":500},"message_summary":{"type":"string","maxLength":2000},"email_message_id":{"type":"string","maxLength":998,"description":"RFC 5322 Message-ID of the email represented by this event (outbound or inbound). Pass this on every email-channel event so future reads can stitch threads."},"email_in_reply_to":{"type":"string","maxLength":998,"description":"Value of the In-Reply-To header when this event records a reply to a prior email."},"email_thread_id":{"type":"string","maxLength":512,"description":"Provider-level thread id (e.g. Gmail threadId) when the event source groups messages server-side."},"ai_agent":{"type":"string","maxLength":64,"default":"hermes-cs","description":"Identifier for the AI agent recording the event"},"metadata":{"type":"object","additionalProperties":true,"description":"Free-form metadata merged into the event record. Prefer the typed email_* fields above over stashing threading info here."}}},"ConservationEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["CONSERVATION_OUTREACH"]},"agent_id":{"type":"string","format":"uuid"},"application_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"metadata":{"type":"object","additionalProperties":true}}},"ConservationEventResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ConservationEvent"},"auth_type":{"type":"string","enum":["api_key","session"]},"message":{"type":"string"}}},"PolicySummary":{"type":"object","properties":{"application_id":{"type":"string","format":"uuid"},"proposed_insured":{"type":"string"},"carrier":{"type":"string","nullable":true},"policy_number":{"type":"string","nullable":true},"signed_url":{"type":"string","format":"uri","nullable":true,"description":"1-hour signed URL to the PDF in the private policy-summaries bucket."},"storage_path":{"type":"string","description":"Path inside the policy-summaries bucket: {agent_id}/{phone_or_unknown}/{application_id}.pdf"},"template_version":{"type":"integer"},"generated_at":{"type":"string","format":"date-time"},"regenerated_count":{"type":"integer","nullable":true},"household_id":{"type":"string","format":"uuid","nullable":true},"client_phone_normalized":{"type":"string","nullable":true},"client_email_normalized":{"type":"string","nullable":true}},"required":["application_id","proposed_insured","storage_path","template_version","generated_at"]},"PolicySummaryResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PolicySummary"},"auth_type":{"type":"string","enum":["api_key","session"]},"scope":{"type":"string","enum":["owner","full_access"]}},"required":["data"]},"PolicySummaryListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PolicySummary"}},"count":{"type":"integer"},"auth_type":{"type":"string","enum":["api_key","session"]},"scope":{"type":"string","enum":["owner","full_access"]}},"required":["data","count"]},"ApiKeyMetadata":{"type":"object","description":"Non-secret metadata for a single API key. The plaintext key value is never returned here.","required":["id","prefix","label","scopes","created_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Internal UUID of the API key record."},"prefix":{"type":"string","description":"Short display prefix, e.g. `lc_8af2...`."},"label":{"type":"string","description":"Human-readable label set at creation."},"scopes":{"type":"array","items":{"type":"string"},"description":"Scopes granted to this key."},"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"Expiry timestamp, or null if the key never expires."},"last_used_at":{"type":"string","format":"date-time","nullable":true,"description":"When the key was last used to authenticate."},"cli_paired_at":{"type":"string","format":"date-time","nullable":true,"description":"When the CLI was first paired with this key."},"created_at":{"type":"string","format":"date-time"}}},"ApiKeyCreateRequest":{"type":"object","description":"Request body for creating a new API key.","required":["label","scopes"],"properties":{"label":{"type":"string","minLength":1,"description":"Human-readable label for the key."},"scopes":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Scopes to grant. Must be valid `api_key_scope` enum values."},"expires_in_days":{"type":"integer","minimum":1,"nullable":true,"description":"Optional TTL in days. Omit for a non-expiring key."}}},"ApiKeyCreateResponse":{"type":"object","description":"Metadata for the newly created key plus the plaintext key value (shown once only).","required":["id","prefix","label","scopes","created_at","key"],"properties":{"id":{"type":"string","format":"uuid"},"prefix":{"type":"string"},"label":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"expires_at":{"type":"string","format":"date-time","nullable":true},"last_used_at":{"type":"string","format":"date-time","nullable":true},"cli_paired_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"key":{"type":"string","pattern":"^lc_[A-Za-z0-9_-]+$","description":"Plaintext API key. This is the ONLY response that includes it — store it immediately."}}},"WhoamiResponse":{"type":"object","description":"Metadata about the authenticated API key, returned by GET /api/v1/whoami.","required":["key_id","key_prefix","scopes","rate_limits","expires_at","label","organization_id","min_supported_cli_version","cli_paired_at"],"properties":{"key_id":{"type":"string","description":"Internal UUID of the API key record."},"key_prefix":{"type":"string","description":"Short display prefix of the key (e.g. `lc_8af2...`).","example":"lc_8af2..."},"scopes":{"type":"array","items":{"type":"string"},"description":"Scopes granted to this key."},"rate_limits":{"type":"object","required":["hourly","daily"],"properties":{"hourly":{"type":"integer","description":"Max requests per hour."},"daily":{"type":"integer","description":"Max requests per day."}}},"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 expiry timestamp, or null if the key never expires."},"label":{"type":"string","description":"Human-readable label set by the user."},"organization_id":{"type":"string","format":"uuid","description":"UUID of the owning organization."},"min_supported_cli_version":{"type":"string","description":"Minimum CLI version supported by this server (semver).","example":"0.1.0"},"cli_paired_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of when the CLI was first paired with this key, or null if never paired."}}}}}}