{
  "openapi": "3.0.3",
  "info": {
    "title": "Callston Click2Call API",
    "version": "1.0.0",
    "description": "Embed Callston calling in your own platform. Server-key endpoints are called by your backend; widget endpoints by the embedded SDK; engine endpoints by the Callston call window. See /developers/click2call for the guide."
  },
  "servers": [{ "url": "https://callston.com/api/v1/click2call" }],
  "components": {
    "securitySchemes": {
      "serverKey": { "type": "http", "scheme": "bearer", "description": "Integration server key (c2ck_…), backend only." },
      "widgetToken": { "type": "http", "scheme": "bearer", "description": "Short-lived widget token (c2cw_…) for one agent on one origin." },
      "engineToken": { "type": "http", "scheme": "bearer", "description": "Call window token (c2ce_…)." }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "ok": { "type": "boolean", "example": false }, "error": { "type": "string" }, "message": { "type": "string" } } },
      "Agent": { "type": "object", "required": ["type", "value"], "properties": { "type": { "type": "string", "enum": ["email", "uuid"] }, "value": { "type": "string" } } },
      "Display": { "type": "object", "properties": { "name": { "type": "string", "nullable": true }, "company": { "type": "string", "nullable": true }, "number": { "type": "string", "nullable": true, "description": "null when the policy hides numbers; masked (•••) when masked" }, "contact_reference": { "type": "string", "nullable": true }, "department": { "type": "string", "nullable": true } } },
      "Policy": { "type": "object", "properties": { "outbound": { "type": "boolean" }, "inbound": { "type": "boolean" }, "number_visibility": { "type": "string", "enum": ["show", "hide", "masked"] }, "controls": { "type": "array", "items": { "type": "string", "enum": ["mute", "dtmf"] } }, "widget": { "type": "object" }, "max_call_seconds": { "type": "integer" } } },
      "Engine": { "type": "object", "nullable": true, "properties": { "id": { "type": "string" }, "state": { "type": "string", "enum": ["enabling", "ready", "offline", "closed"] }, "live": { "type": "boolean" }, "available": { "type": "boolean" }, "mic_ready": { "type": "boolean" }, "busy": { "type": "boolean" }, "muted": { "type": "boolean" }, "simulated": { "type": "boolean" }, "last_seen_ago": { "type": "integer", "nullable": true }, "version": { "type": "integer" } } },
      "Call": { "type": "object", "properties": { "request_id": { "type": "string" }, "call_id": { "type": "string" }, "direction": { "type": "string", "enum": ["outbound", "inbound"] }, "status": { "type": "string", "enum": ["queued", "claimed", "dialing", "ringing", "connected", "completed", "rejected", "busy", "no_answer", "canceled", "failed", "expired", "incoming"] }, "error": { "type": "string", "nullable": true }, "display": { "$ref": "#/components/schemas/Display" }, "answered_at": { "type": "integer", "nullable": true, "description": "Unix time of the authoritative answer moment" }, "ended_at": { "type": "integer", "nullable": true }, "duration": { "type": "integer", "description": "Seconds, server-computed" }, "expires_at": { "type": "integer" }, "server_time": { "type": "integer" } } },
      "CallRequest": { "type": "object", "required": ["integration_id", "agent", "phone_number"], "properties": { "integration_id": { "type": "string" }, "agent": { "$ref": "#/components/schemas/Agent" }, "phone_number": { "type": "string", "description": "E.164" }, "contact_reference": { "type": "string", "maxLength": 120 }, "display_name": { "type": "string", "maxLength": 120 }, "display_company": { "type": "string", "maxLength": 120 }, "metadata": { "type": "object", "description": "≤ 2000 bytes serialized" } } },
      "SessionRequest": { "type": "object", "required": ["integration_id", "agent", "origin"], "properties": { "integration_id": { "type": "string" }, "agent": { "$ref": "#/components/schemas/Agent" }, "origin": { "type": "string", "example": "https://crm.example.com" } } },
      "Session": { "type": "object", "properties": { "ok": { "type": "boolean" }, "widget_token": { "type": "string" }, "expires_at": { "type": "integer" }, "agent": { "type": "object", "properties": { "name": { "type": "string" }, "identifier": { "type": "string" } } }, "policy": { "$ref": "#/components/schemas/Policy" }, "engine": { "$ref": "#/components/schemas/Engine" } } },
      "WidgetState": { "type": "object", "properties": { "ok": { "type": "boolean" }, "engine": { "$ref": "#/components/schemas/Engine" }, "call": { "$ref": "#/components/schemas/Call" }, "policy": { "$ref": "#/components/schemas/Policy" }, "agent": { "type": "object" }, "version": { "type": "integer" }, "server_time": { "type": "integer" }, "poll_ms": { "type": "integer" }, "session_expires_at": { "type": "integer" } } },
      "Command": { "type": "object", "required": ["kind", "request_id"], "properties": { "kind": { "type": "string", "enum": ["hangup", "answer", "decline", "mute", "unmute", "dtmf", "availability", "focus"] }, "request_id": { "type": "string", "description": "Client-generated; the same id is never executed twice" }, "digits": { "type": "string" }, "available": { "type": "boolean" }, "ref": { "type": "string" } } }
    },
    "responses": {
      "Err": { "description": "Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  },
  "paths": {
    "/sessions": { "post": { "summary": "Create a widget session for one agent on one origin", "security": [{ "serverKey": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionRequest" } } } }, "responses": { "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Session" } } } }, "401": { "$ref": "#/components/responses/Err" }, "403": { "$ref": "#/components/responses/Err" }, "404": { "$ref": "#/components/responses/Err" }, "409": { "$ref": "#/components/responses/Err" }, "422": { "$ref": "#/components/responses/Err" }, "429": { "$ref": "#/components/responses/Err" } } } },
    "/calls": { "post": { "summary": "Request an outgoing call (server-to-server; 202 = queued, not answered)", "security": [{ "serverKey": [] }], "parameters": [{ "name": "Idempotency-Key", "in": "header", "schema": { "type": "string", "maxLength": 120 }, "description": "Same key + same body → same intent; same key + different body → 409" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CallRequest" } } } }, "responses": { "202": { "description": "Queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Call" } } } }, "200": { "description": "Replayed (idempotent)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Call" } } } }, "402": { "$ref": "#/components/responses/Err" }, "403": { "$ref": "#/components/responses/Err" }, "409": { "$ref": "#/components/responses/Err" }, "422": { "$ref": "#/components/responses/Err" }, "429": { "$ref": "#/components/responses/Err" } } } },
    "/calls/{id}": { "get": { "summary": "Call status", "security": [{ "serverKey": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Call" } } } }, "404": { "$ref": "#/components/responses/Err" } } } },
    "/calls/{id}/hangup": { "post": { "summary": "End a call", "security": [{ "serverKey": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Ending / ended", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Call" } } } }, "404": { "$ref": "#/components/responses/Err" }, "502": { "$ref": "#/components/responses/Err" } } } },
    "/widget/state": { "get": { "summary": "The agent's state (engine, call, policy)", "security": [{ "widgetToken": [] }], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WidgetState" } } } }, "401": { "$ref": "#/components/responses/Err" }, "403": { "$ref": "#/components/responses/Err" } } } },
    "/widget/launch-ticket": { "post": { "summary": "Single-use ticket to open the call window", "security": [{ "widgetToken": [] }], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ticket": { "type": "string" }, "window_url": { "type": "string" }, "expires_in": { "type": "integer" }, "window_name": { "type": "string" } } } } } } } } },
    "/widget/commands": { "post": { "summary": "Send a control to the agent's call window", "security": [{ "widgetToken": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Command" } } } }, "responses": { "200": { "description": "Accepted", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string" }, "command_id": { "type": "integer" } } } } } }, "403": { "$ref": "#/components/responses/Err" }, "409": { "$ref": "#/components/responses/Err" }, "422": { "$ref": "#/components/responses/Err" } } } },
    "/widget/refresh": { "post": { "summary": "A fresh widget token", "security": [{ "widgetToken": [] }], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "widget_token": { "type": "string" }, "expires_at": { "type": "integer" } } } } } } } } },
    "/widget/revoke": { "post": { "summary": "End the widget session (not the call)", "security": [{ "widgetToken": [] }], "responses": { "200": { "description": "OK" } } } },
    "/engine/pair": { "post": { "summary": "Internal: consume a launch ticket and become the engine", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "ticket": { "type": "string" }, "simulated": { "type": "boolean" } } } } } }, "responses": { "201": { "description": "Paired" }, "401": { "$ref": "#/components/responses/Err" }, "409": { "$ref": "#/components/responses/Err" } } } },
    "/engine/voice-token": { "post": { "summary": "Internal: provider token for the engine identity", "security": [{ "engineToken": [] }], "responses": { "200": { "description": "OK" }, "503": { "$ref": "#/components/responses/Err" } } } },
    "/engine/heartbeat": { "post": { "summary": "Internal: state report in, commands and dial requests out", "security": [{ "engineToken": [] }], "responses": { "200": { "description": "OK" } } } },
    "/engine/claim": { "post": { "summary": "Internal: atomically claim an intent", "security": [{ "engineToken": [] }], "responses": { "200": { "description": "OK" }, "404": { "$ref": "#/components/responses/Err" } } } },
    "/engine/hangup": { "post": { "summary": "Internal: hang up from the window", "security": [{ "engineToken": [] }], "responses": { "200": { "description": "OK" } } } },
    "/engine/close": { "post": { "summary": "Internal: the window is closing", "security": [{ "engineToken": [] }], "responses": { "200": { "description": "OK" } } } }
  }
}
