{
  "openapi": "3.1.0",
  "info": {
    "title": "Kalyvox Public API (Zapier)",
    "version": "1.0.0",
    "description": "Public REST API used by the Kalyvox Zapier integration. Every call is authenticated with a workspace API key (Bearer token) generated from Kalyvox > Settings > Integrations > Zapier. A key identifies one workspace; the tenant is never taken from client input.",
    "contact": {
      "name": "Kalyvox Support",
      "email": "app@kalyvox.ai",
      "url": "https://kalyvox.ai/aide/api-kalyvox-zapier"
    },
    "termsOfService": "https://kalyvox.ai/cgu"
  },
  "servers": [
    {
      "url": "https://auth.kalyvox.ai/functions/v1/zapier-api",
      "description": "Production"
    }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Authentication" },
    { "name": "Calls" },
    { "name": "Webhooks" }
  ],
  "paths": {
    "/v1/auth/test": {
      "get": {
        "tags": ["Authentication"],
        "operationId": "authTest",
        "summary": "Validate an API key",
        "description": "Returns the workspace bound to the API key. Used by Zapier as the authentication test.",
        "responses": {
          "200": {
            "description": "The authenticated workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["id", "name", "brand"],
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "name": { "type": "string", "nullable": true },
                    "brand": { "type": "string", "examples": ["kalyvox"] }
                  }
                },
                "example": { "id": "3f1c...", "name": "Acme Plumbing", "brand": "kalyvox" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/tickets/recent": {
      "get": {
        "tags": ["Calls"],
        "operationId": "listRecentTickets",
        "summary": "List the most recent call tickets",
        "description": "Returns the newest call tickets of the workspace, newest first. Used by Zapier to build trigger samples.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Number of tickets to return (1-100).",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 3 }
          }
        ],
        "responses": {
          "200": {
            "description": "An array of tickets, newest first.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Ticket" } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/tickets/search": {
      "get": {
        "tags": ["Calls"],
        "operationId": "searchTickets",
        "summary": "Search call tickets",
        "description": "Filter the workspace tickets and page through the results with an opaque cursor.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "created_after",
            "in": "query",
            "description": "ISO 8601 date-time; keeps tickets created at or after this instant.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "created_before",
            "in": "query",
            "description": "ISO 8601 date-time; keeps tickets created at or before this instant.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["open", "in_progress", "closed", "pending", "resolved"]
            }
          },
          {
            "name": "urgency",
            "in": "query",
            "schema": { "type": "string", "enum": ["normal", "high"] }
          },
          {
            "name": "intent",
            "in": "query",
            "description": "Intent key configured in the workspace (max 80 chars).",
            "schema": { "type": "string", "maxLength": 80 }
          },
          {
            "name": "caller_phone",
            "in": "query",
            "description": "Exact caller phone number in E.164 format (max 32 chars).",
            "schema": { "type": "string", "maxLength": 32 }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor returned as next_cursor by a previous call.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of tickets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["results", "next_cursor"],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Ticket" }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Pass back as ?cursor= to fetch the next page. null when the last page is reached."
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/webhook-subscriptions": {
      "post": {
        "tags": ["Webhooks"],
        "operationId": "createWebhookSubscription",
        "summary": "Subscribe to an event (REST hook)",
        "description": "Registers a Zapier hook URL. Kalyvox POSTs the ticket payload to that URL when the event occurs, with automatic retries (up to 5 attempts, exponential backoff).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["target_url"],
                "properties": {
                  "target_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS Zapier hook URL (hooks.zapier.com). Aliases accepted: hookUrl, url."
                  },
                  "event": {
                    "type": "string",
                    "enum": ["ticket.created"],
                    "default": "ticket.created"
                  }
                }
              },
              "example": {
                "target_url": "https://hooks.zapier.com/hooks/standard/123/abc/",
                "event": "ticket.created"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "event": { "type": "string" },
                    "target_url": { "type": "string", "format": "uri" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/webhook-subscriptions/{id}": {
      "delete": {
        "tags": ["Webhooks"],
        "operationId": "deleteWebhookSubscription",
        "summary": "Unsubscribe from an event",
        "description": "Deactivates the subscription and cancels its pending deliveries.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "active": { "type": "boolean", "examples": [false] }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "webhooks": {
    "ticket.created": {
      "post": {
        "summary": "New call ticket",
        "description": "Sent to the subscribed target_url when a new call ticket is created. The body uses the exact same schema as /v1/tickets/recent items. Respond with 2xx; non-2xx responses are retried up to 5 times (30s, 2min, 10min, 1h, 6h).",
        "requestBody": {
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/Ticket" } }
          }
        },
        "responses": { "200": { "description": "Acknowledged." } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Workspace API key: Authorization: Bearer kvx_live_..."
      }
    },
    "schemas": {
      "Ticket": {
        "type": "object",
        "description": "A call ticket created by the Kalyvox phone assistant.",
        "required": ["id", "created_at", "appointment_booked", "metadata"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "created_at": { "type": "string", "format": "date-time" },
          "status": {
            "type": "string",
            "nullable": true,
            "enum": ["open", "in_progress", "closed", "pending", "resolved", null]
          },
          "intent": {
            "type": "string",
            "nullable": true,
            "description": "Intent key detected during the call."
          },
          "urgency": { "type": "string", "nullable": true, "enum": ["normal", "high", null] },
          "caller_name": { "type": "string", "nullable": true },
          "caller_phone": { "type": "string", "nullable": true, "description": "E.164 format." },
          "caller_email": {
            "type": "string",
            "nullable": true,
            "description": "Present when an appointment was booked with an email."
          },
          "summary": { "type": "string", "nullable": true },
          "call_id": { "type": "string", "nullable": true },
          "appointment_booked": { "type": "boolean" },
          "appointment_start": { "type": "string", "format": "date-time", "nullable": true },
          "appointment_end": { "type": "string", "format": "date-time", "nullable": true },
          "transcript": {
            "type": "string",
            "nullable": true,
            "description": "Plain-text transcript, one line per turn."
          },
          "language": { "type": "string", "nullable": true, "examples": ["en", "fr"] },
          "metadata": {
            "type": "object",
            "properties": {
              "sub_intent_key": { "type": "string", "nullable": true },
              "is_escalated": { "type": "boolean" },
              "assigned": { "type": "boolean" }
            }
          }
        },
        "example": {
          "id": "0f0a9f6c-4c4e-4f5f-9a2b-2a1f4c0f9a11",
          "created_at": "2026-08-27T09:14:02.318Z",
          "status": "open",
          "intent": "new_lead",
          "urgency": "high",
          "caller_name": "Jane Cooper",
          "caller_phone": "+13125550142",
          "caller_email": "jane@example.com",
          "summary": "Water heater leaking, asks for an emergency visit today.",
          "call_id": "call_8fa2",
          "appointment_booked": true,
          "appointment_start": "2026-08-27T15:00:00.000Z",
          "appointment_end": "2026-08-27T16:00:00.000Z",
          "transcript": "assistant: Thanks for calling Acme Plumbing...\nuser: My water heater is leaking.",
          "language": "en",
          "metadata": { "sub_intent_key": "emergency", "is_escalated": false, "assigned": false }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "request_id"],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unauthorized",
                  "invalid_request",
                  "not_found",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": { "type": "string" },
              "request_id": {
                "type": "string",
                "description": "Also returned in the x-request-id response header."
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameters.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing, invalid or revoked API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource not found for this workspace.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "More than 120 requests per minute for this API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
