{
  "openapi": "3.1.0",
  "info": {
    "title": "Voxplo Agent API",
    "version": "1.0.0",
    "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."
  },
  "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."
          }
        }
      }
    }
  }
}