{
  "openapi": "3.1.0",
  "x-versioning-policy": "https://www.voxplo.ai/docs/agent-api/versioning",
  "x-rate-limit-headers": [
    "RateLimit-Limit",
    "RateLimit-Remaining",
    "RateLimit-Reset",
    "RateLimit-Policy",
    "Retry-After"
  ],
  "externalDocs": {
    "description": "Voxplo Agent API documentation",
    "url": "https://www.voxplo.ai/docs/agent-api"
  },
  "info": {
    "title": "Voxplo Agent API",
    "version": "1.0.0",
    "x-versioning-policy": "https://www.voxplo.ai/docs/agent-api/versioning",
    "description": "REST API for placing and tracking outbound AI phone calls. Authenticate with an agent API key (bys_ak_...) from your Developers dashboard. All paths are relative to https://api.voxplo.ai/v1. Reliability: pass an Idempotency-Key header on writes; every response carries RFC RateLimit headers (and Retry-After on 429). Versioning & deprecation: the API is versioned in the URL path (/v1); breaking changes ship under a new major version and deprecations are announced at least 90 days in advance by email and in the changelog, with the prior version served through the sunset window. Full policy and the exact Deprecation/Sunset/Link headers: https://www.voxplo.ai/docs/agent-api/versioning"
  },
  "servers": [
    {
      "url": "https://api.voxplo.ai/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/agent/calls": {
      "post": {
        "operationId": "placeCall",
        "summary": "Place an outbound AI call",
        "description": "Validates the request, runs guardrails, creates a call record with status queued, and returns immediately. The call runs asynchronously. Guardrail failures return a 4xx before any call is placed.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional client-supplied idempotency key. Retrying a POST with the same key returns the original result instead of placing a second call, so a network retry can never double-dial."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlaceCallRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Call accepted and queued. The call runs asynchronously; poll GET /agent/calls/{id} (see the Location header) for the transcript and outcome. Every response also carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers.",
            "headers": {
              "Location": {
                "description": "Relative URL to poll for this call's result.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Requests permitted per window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "callId",
                    "status",
                    "language",
                    "languageSource"
                  ],
                  "properties": {
                    "callId": {
                      "type": "string",
                      "description": "Unique call identifier (UUID)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued"
                      ],
                      "description": "Always queued on a successful placement."
                    },
                    "language": {
                      "type": "string",
                      "description": "Resolved BCP-47 language code the call will use."
                    },
                    "languageSource": {
                      "type": "string",
                      "enum": [
                        "explicit",
                        "inferred",
                        "auto"
                      ],
                      "description": "How the language was resolved: explicit (caller passed it), inferred (from objective), auto (mirrors recipient)."
                    },
                    "voice": {
                      "type": "string",
                      "nullable": true,
                      "description": "Voice name the call will use, e.g. Aoede."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (validation or guardrail failure).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited or minute cap exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Carrier or engine could not place the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Temporary service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listCalls",
        "summary": "List recent calls",
        "description": "Returns recent calls for your account, most recent first. Each item is a summary.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            },
            "description": "Maximum number of calls to return. Max 100."
          }
        ],
        "responses": {
          "200": {
            "description": "List of call summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calls"
                  ],
                  "properties": {
                    "calls": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentCallSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent/calls/{id}": {
      "get": {
        "operationId": "getCall",
        "summary": "Get a call",
        "description": "Returns the current state of a call. Calls are tenant-scoped: you can only fetch calls placed by your own key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The callId returned by POST /agent/calls."
          }
        ],
        "responses": {
          "200": {
            "description": "Call record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentCall"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Agent API key. Create keys in your account under Developers. Format: Authorization: Bearer bys_ak_..."
      }
    },
    "schemas": {
      "PlaceCallRequest": {
        "type": "object",
        "required": [
          "to",
          "objective"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Destination phone number in E.164 format, e.g. +14155550123."
          },
          "objective": {
            "type": "string",
            "description": "What the AI should accomplish on the call. Write it as a clear goal in plain text."
          },
          "context": {
            "type": "string",
            "nullable": true,
            "description": "Background information the AI should know before the call. Not shared with the recipient."
          },
          "fields": {
            "type": "array",
            "nullable": true,
            "maxItems": 20,
            "description": "Structured fields to extract from the transcript. Up to 20 items.",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Field key name."
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "string",
                    "boolean",
                    "number"
                  ],
                  "description": "Value type. Defaults to string."
                },
                "description": {
                  "type": "string",
                  "description": "What the field means; guides extraction."
                }
              }
            }
          },
          "webhookUrl": {
            "type": "string",
            "nullable": true,
            "description": "HTTPS URL to receive signed webhook events during and after the call."
          },
          "callerId": {
            "type": "string",
            "nullable": true,
            "description": "Caller ID override. Must be a number on your account (E.164). Defaults to the number set in your Developers dashboard."
          },
          "voice": {
            "type": "string",
            "nullable": true,
            "description": "The voice the AI uses on the call, e.g. Aoede, Kore, Puck."
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "BCP-47 language code (e.g. en-US, ru-RU) or auto. Omit to let Voxplo infer from the objective."
          }
        }
      },
      "AgentCall": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The call ID."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "dialing",
              "in_progress",
              "completed",
              "no_answer",
              "voicemail",
              "declined",
              "failed"
            ],
            "description": "Current call status."
          },
          "to": {
            "type": "string",
            "nullable": true,
            "description": "Destination number (E.164)."
          },
          "objective": {
            "type": "string",
            "nullable": true,
            "description": "The objective you supplied."
          },
          "voice": {
            "type": "string",
            "nullable": true,
            "description": "The voice the AI used on the call."
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "The language the AI spoke (BCP-47, e.g. en-US)."
          },
          "languageSource": {
            "type": "string",
            "nullable": true,
            "enum": [
              "explicit",
              "inferred",
              "auto"
            ],
            "description": "How the language was chosen."
          },
          "summary": {
            "type": "string",
            "nullable": true,
            "description": "Plain-English summary. Set on completed."
          },
          "transcript": {
            "type": "string",
            "nullable": true,
            "description": "Full transcript of the conversation."
          },
          "extracted": {
            "type": "object",
            "nullable": true,
            "description": "Extracted field values keyed by field name.",
            "additionalProperties": true
          },
          "recordingUrl": {
            "type": "string",
            "nullable": true,
            "description": "Authenticated URL to the call recording."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO 8601 timestamp when the call was answered."
          },
          "endedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO 8601 timestamp when the call ended."
          },
          "durationSec": {
            "type": "number",
            "nullable": true,
            "description": "Call duration in seconds."
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Error token on failed calls."
          }
        }
      },
      "AgentCallSummary": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The call ID."
          },
          "to": {
            "type": "string",
            "nullable": true,
            "description": "Destination number (E.164)."
          },
          "objective": {
            "type": "string",
            "nullable": true,
            "description": "The objective supplied at placement."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "dialing",
              "in_progress",
              "completed",
              "no_answer",
              "voicemail",
              "declined",
              "failed"
            ],
            "description": "Current call status."
          },
          "summary": {
            "type": "string",
            "nullable": true,
            "description": "Plain-English summary. Set on completed."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "durationSec": {
            "type": "number",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Error token on failed calls."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error token, e.g. to_required, destination_blocked, unauthorized."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation of the error."
          }
        }
      }
    }
  }
}