{
  "openapi": "3.1.0",
  "info": {
    "title": "Sarj.ai Developer API",
    "description": "Public API for programmatic access to the **Sarj.ai voice platform**.\n\n> [!TIP]\n> **New here?** Start with the [Getting Started guide](https://platform-docs.sarj.ai/getting-started)\n> to go from API key to first call in under 5 minutes.\n>\n> **Building with AI agents?** See the [MCP Server guide](https://platform-docs.sarj.ai/mcp-server)\n> to connect Claude Code, Cursor, or any MCP-compatible agent.\n\n---\n\n## Authentication\n\nAll authenticated endpoints require a **Bearer token** in the `Authorization`\nheader:\n\n```\nAuthorization: Bearer <your-api-key>\n```\n\nGenerate an API key from [platform.sarj.ai/api-keys](https://platform.sarj.ai/api-keys).\n\n> [!WARNING]\n> If you receive a `401 Unauthorized` response, your key may be invalid,\n> expired, or missing. Check the `error.type` field in the response body.\n\n---\n\n## Response Format\n\n**Success**\n\n```json\n{\n  \"data\": { ... },\n  \"meta\": { \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\" }\n}\n```\n\n**Error**\n\n```json\n{\n  \"error\": {\n    \"type\": \"unauthorized\",\n    \"message\": \"Authentication required.\"\n  },\n  \"meta\": { \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\" }\n}\n```\n\nAlways branch on `error.type` — the `message` field is for humans only.\n\n---\n\n## Support\n\nInclude `meta.request_id` in all support tickets for faster resolution.\n",
    "license": {
      "name": "Apache 2.0",
      "identifier": "Apache-2.0"
    },
    "version": "1.1.0"
  },
  "servers": [
    {
      "url": "https://platform-api.sarj.ai/api/v1",
      "description": "Production"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Health Check",
        "description": "Returns the operational status of the API. Does not require authentication.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_HealthStatus_"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [],
        "x-codeSamples": [
          {
            "label": "Python (SDK)",
            "lang": "Python",
            "source": "from sarj_platform_sdk import SDK\n\nwith SDK(timeout_ms=10_000) as sdk:\n    print(sdk.system.get_health().data.status)  # \"ok\""
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://platform-api.sarj.ai/api/v1/health\", timeout=10\n)\nresponse.raise_for_status()\nprint(response.json())"
          },
          {
            "label": "TypeScript",
            "lang": "TypeScript",
            "source": "const response = await fetch(\n  \"https://platform-api.sarj.ai/api/v1/health\",\n  { signal: AbortSignal.timeout(10_000) },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst data = await response.json();\nconsole.log(data);"
          }
        ]
      }
    },
    "/calls": {
      "post": {
        "tags": [
          "Calls"
        ],
        "summary": "Place an outbound Sarj.ai voice call",
        "description": "Place an outbound Sarj.ai voice call: dials the given phone_number and runs the specified scenario (with optional template variables and language). Returns a call_id that can be polled via getCall. Use this whenever the user asks to call, dial, ring, or phone someone through Sarj.ai — do not attempt to start calls by writing to the database or invoking telephony providers directly.",
        "operationId": "createCall",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCallRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_PublicCall_"
                },
                "example": {
                  "data": {
                    "id": "8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b",
                    "status": "queued",
                    "phone_number": "+966512345678",
                    "scenario_id": "550e8400-e29b-41d4-a716-446655440000",
                    "created_at": "2026-04-12T10:30:00Z"
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Outbound call failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "outbound_call_failed",
                    "message": "Outbound call could not be initiated.",
                    "reason": "telephony_provider_unavailable"
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "unauthorized",
                    "message": "Authentication required."
                  },
                  "meta": {}
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — phone number blocked, scenario access denied, or scheduling (scheduled_at or schedule_config_id) is disabled on this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "phone_number_blocked": {
                    "summary": "Phone number is blocked",
                    "value": {
                      "error": {
                        "type": "phone_number_blocked",
                        "message": "Phone number +966512345678 is blocked.",
                        "phone_number": "+966512345678"
                      },
                      "meta": {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000"
                      }
                    }
                  },
                  "scenario_forbidden": {
                    "summary": "Scenario belongs to a different organization",
                    "value": {
                      "error": {
                        "type": "scenario_forbidden",
                        "message": "Access denied to scenario 550e8400-e29b-41d4-a716-446655440000.",
                        "scenario_id": "550e8400-e29b-41d4-a716-446655440000"
                      },
                      "meta": {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000"
                      }
                    }
                  },
                  "scheduling_disabled": {
                    "summary": "Call scheduling is not enabled on this environment",
                    "value": {
                      "error": {
                        "type": "scheduling_disabled",
                        "message": "Call scheduling is not enabled on this environment."
                      },
                      "meta": {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Scenario not found, or the requested schedule_config_id does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "scenario_not_found": {
                    "summary": "Scenario does not exist",
                    "value": {
                      "error": {
                        "type": "scenario_not_found",
                        "message": "Scenario 550e8400-e29b-41d4-a716-446655440000 was not found.",
                        "scenario_id": "550e8400-e29b-41d4-a716-446655440000"
                      },
                      "meta": {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000"
                      }
                    }
                  },
                  "schedule_config_not_found": {
                    "summary": "Requested schedule_config_id does not exist",
                    "value": {
                      "error": {
                        "type": "schedule_config_not_found",
                        "message": "Schedule config 01890000-0000-7000-8000-000000000000 was not found.",
                        "schedule_config_id": "01890000-0000-7000-8000-000000000000"
                      },
                      "meta": {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000"
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "validation_error",
                    "message": "Request validation failed.",
                    "field_violations": [
                      {
                        "field": "body.phone_number",
                        "message": "value is not a valid phone number"
                      }
                    ]
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Call limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "call_limit_exceeded",
                    "message": "Call limit of 10 reached for phone number +966512345678.",
                    "phone_number": "+966512345678",
                    "call_limit": 10
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "Python (SDK)",
            "lang": "Python",
            "source": "import os\nfrom sarj_platform_sdk import SDK\n\nwith SDK(api_key_auth=os.environ[\"SARJ_API_KEY\"], timeout_ms=10_000) as sdk:\n    res = sdk.calls.create_call(\n        phone_number=os.environ[\"TEST_PHONE_NUMBER\"],\n        scenario_id=os.environ[\"SARJ_SCENARIO_ID\"],\n        language=\"ar\",\n    )\n    print(f\"Call queued: {res.data.id}\")"
          },
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl --fail-with-body --max-time 10 https://platform-api.sarj.ai/api/v1/calls \\\n  -H \"Authorization: Bearer $SARJ_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"phone_number\": \"+966512345678\",\n    \"scenario_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"language\": \"ar\"\n  }'"
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport requests\n\nresponse = requests.post(\n    \"https://platform-api.sarj.ai/api/v1/calls\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['SARJ_API_KEY']}\",\n    },\n    json={\n        \"phone_number\": os.environ[\"TEST_PHONE_NUMBER\"],\n        \"scenario_id\": os.environ[\"SARJ_SCENARIO_ID\"],\n        \"language\": \"ar\",\n    },\n    timeout=10,\n)\nresponse.raise_for_status()\ncall = response.json()[\"data\"]\nprint(f\"Call queued: {call['id']}\")"
          },
          {
            "label": "TypeScript",
            "lang": "TypeScript",
            "source": "const response = await fetch(\"https://platform-api.sarj.ai/api/v1/calls\", {\n  method: \"POST\",\n  headers: {\n    \"Authorization\": `Bearer ${process.env.SARJ_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    phone_number: process.env.TEST_PHONE_NUMBER,\n    scenario_id: process.env.SARJ_SCENARIO_ID,\n    language: \"ar\",\n  }),\n  signal: AbortSignal.timeout(10_000),\n});\n\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { data: call } = await response.json();\nconsole.log(`Call queued: ${call.id}`);"
          }
        ]
      }
    },
    "/calls/{call_id}/cancel": {
      "post": {
        "tags": [
          "Calls"
        ],
        "summary": "Cancel a pending scheduled Sarj.ai call",
        "description": "Cancel a call that is still pending in status 'scheduled' — a booked future call or a platform-booked retry attempt. This is also the per-person stop-calling lever: cancelling a pending retry ends its retry chain. Once a call has been released for dialing it can no longer be cancelled and this returns 409.",
        "operationId": "cancelScheduledCall",
        "parameters": [
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Call id (UUID), as returned in the `id` field.",
              "title": "Call Id"
            },
            "description": "Call id (UUID), as returned in the `id` field."
          }
        ],
        "responses": {
          "200": {
            "description": "The call was cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_PublicCall_"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Scheduling disabled, or no organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The call is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/calls/{call_id}/reschedule": {
      "post": {
        "tags": [
          "Calls"
        ],
        "summary": "Move a pending scheduled Sarj.ai call to a new time",
        "description": "Update scheduled_at on a call that is still pending in status 'scheduled'. The new time must be at least 10 minutes ahead and within 30 days. Once released for dialing, rescheduling returns 409.",
        "operationId": "rescheduleScheduledCall",
        "parameters": [
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Call id (UUID), as returned in the `id` field.",
              "title": "Call Id"
            },
            "description": "Call id (UUID), as returned in the `id` field."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RescheduleCallRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The call was rescheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_PublicCall_"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Scheduling disabled, or no organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The call is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid scheduled_at.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/calls/{call_id}": {
      "get": {
        "tags": [
          "Calls"
        ],
        "summary": "Fetch Sarj.ai voice call details by call_id",
        "description": "Fetch details of a Sarj.ai voice call by call_id: status, phone_number, scenario_id, language, direction (inbound/outbound), scenario variables, duration (seconds), permanent_recording_url (stable authenticated recording link), recording_url (deprecated signed download link), transcript (list of spoken messages), report (post-call outcome with success-criteria results; generated asynchronously after completed or max_duration_reached calls, otherwise null; see field details for permanent null cases), and timestamps (created_at, updated_at, started_at, ended_at). Use this whenever the user asks about a specific call, phone conversation, or voice interaction by its call_id — do not query other tools.",
        "operationId": "getCall",
        "parameters": [
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Call id (UUID), as returned in the `id` field.",
              "title": "Call Id"
            },
            "description": "Call id (UUID), as returned in the `id` field."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_PublicCallDetail_"
                },
                "example": {
                  "data": {
                    "id": "8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b",
                    "status": "completed",
                    "phone_number": "+966512345678",
                    "scenario_id": "550e8400-e29b-41d4-a716-446655440000",
                    "language": "ar",
                    "direction": "outbound",
                    "variables": {
                      "customer_name": "Ahmad",
                      "order_id": "ORD-123"
                    },
                    "duration": 142,
                    "permanent_recording_url": "https://platform-api.sarj.ai/api/v1/calls/8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b/recording",
                    "transcript": [
                      {
                        "role": "assistant",
                        "content": "Hello Ahmad, calling about order ORD-123."
                      },
                      {
                        "role": "user",
                        "content": "Yes, hi."
                      },
                      {
                        "role": "assistant",
                        "content": "Your order is ready for delivery tomorrow."
                      }
                    ],
                    "report": {
                      "generated_at": "2026-04-12T10:33:41Z",
                      "outcome": {
                        "status": "success",
                        "reason": "The customer confirmed the delivery appointment for tomorrow.",
                        "success_criteria": [
                          {
                            "text": "Customer confirms the delivery slot",
                            "achieved": true,
                            "is_primary": true
                          }
                        ]
                      }
                    },
                    "created_at": "2026-04-12T10:30:00Z",
                    "updated_at": "2026-04-12T10:32:22Z",
                    "started_at": "2026-04-12T10:30:05Z",
                    "ended_at": "2026-04-12T10:32:22Z"
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "unauthorized",
                    "message": "Authentication required."
                  },
                  "meta": {}
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "API key not associated with an organization.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "no_organization",
                    "message": "API key is not associated with an organization."
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "call_not_found",
                    "message": "Call 8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b was not found.",
                    "call_id": "8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b"
                  },
                  "meta": {
                    "request_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "Python (SDK)",
            "lang": "Python",
            "source": "import os\nfrom sarj_platform_sdk import SDK\n\nwith SDK(api_key_auth=os.environ[\"SARJ_API_KEY\"], timeout_ms=10_000) as sdk:\n    res = sdk.calls.get_call(call_id=os.environ[\"SARJ_CALL_ID\"])\n    call = res.data\n    print(f\"Status: {call.status}, duration: {call.duration}\")"
          },
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl --fail-with-body --max-time 10 https://platform-api.sarj.ai/api/v1/calls/8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b \\\n  -H \"Authorization: Bearer $SARJ_API_KEY\""
          },
          {
            "label": "Python (requests)",
            "lang": "Python",
            "source": "import os\nimport requests\n\ncall_id = \"8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b\"\nresponse = requests.get(\n    f\"https://platform-api.sarj.ai/api/v1/calls/{call_id}\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['SARJ_API_KEY']}\",\n    },\n    timeout=10,\n)\nresponse.raise_for_status()\ncall = response.json()[\"data\"]\nprint(f\"Status: {call['status']}, duration: {call['duration']}\")"
          },
          {
            "label": "TypeScript",
            "lang": "TypeScript",
            "source": "const callId = \"8f9b2c1e-4a5d-4f6e-8b1a-2c3d4e5f6a7b\";\nconst response = await fetch(\n  `https://platform-api.sarj.ai/api/v1/calls/${callId}`,\n  {\n    headers: {\n      \"Authorization\": `Bearer ${process.env.SARJ_API_KEY}`,\n    },\n    signal: AbortSignal.timeout(10_000),\n  },\n);\n\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { data: call } = await response.json();\nconsole.log(`Status: ${call.status}, duration: ${call.duration}`);"
          }
        ]
      }
    },
    "/calls/{call_id}/recording": {
      "get": {
        "tags": [
          "Calls"
        ],
        "summary": "Redirect to a fresh download URL for a Sarj.ai call recording",
        "description": "Resolve a Sarj.ai recording link to a freshly minted, short-lived download URL via a 302 redirect. Two ways to authenticate: an API key (the standard `/api/v1` Bearer credential — the request is authorized against the key's organization), or, when no API key is sent, a call-scoped recording token in the `X-Sarj-Recording-Token` header (preferred) or the `t` query parameter. The token exists for header-less consumers such as an HTML5 audio player; prefer the API key or the header, since the `t` query form places a credential in the URL. If an API key is present it always wins and the token is ignored.",
        "operationId": "getCallRecording",
        "parameters": [
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Call id (UUID), as returned in the `id` field.",
              "title": "Call Id"
            },
            "description": "Call id (UUID), as returned in the `id` field."
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Call-scoped recording token (header/API-key alternative).",
              "title": "T"
            },
            "description": "Call-scoped recording token (header/API-key alternative)."
          },
          {
            "name": "x-sarj-recording-token",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sarj-Recording-Token"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect (Location header) to a short-lived recording download URL.",
            "headers": {
              "Location": {
                "description": "Short-lived signed recording download URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            },
            "content": {}
          },
          "401": {
            "description": "Invalid API key, or (token path) a missing/invalid/expired/mismatched token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "API key not associated with an organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found or recording unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {}
        ],
        "x-speakeasy-ignore": true
      }
    },
    "/schedule-configs": {
      "post": {
        "tags": [
          "Schedule Configs"
        ],
        "summary": "Create a Sarj.ai schedule config (retry policy) for outbound calls",
        "description": "Create a reusable retry policy: how many times to retry a call the customer did not answer, how long to wait between attempts, and in which daily window. Reference the returned id as schedule_config_id when creating calls. This endpoint is create-only today — there is no way to fetch, list, update, or disable a config after creation; that ships in a follow-up.",
        "operationId": "createScheduleConfig",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScheduleConfigRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The schedule config was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse_PublicScheduleConfig_"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Scheduling disabled, or no organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error with a request ID for support correlation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "callEnded": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Receive a call outcome",
        "description": "Sarj POSTs this notification to the webhook URL configured in the dashboard. This describes your receiver, not an endpoint on the Sarj API. Acknowledge within 5 seconds after durably storing the event; deduplicate on call_id. payload.type selects complete or a not-completed outcome. complete identifies the started-call payload shape, including voicemail; inspect payload.call_data.status for the outcome. Authentication uses the optional stored credential/header configured for your webhook, not your Sarj API key.",
        "operationId": "receiveCallEnded",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookNotification"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "401": {
            "description": "Your receiver rejected the configured credential."
          },
          "422": {
            "description": "Your receiver rejected an invalid notification."
          },
          "500": {
            "description": "Your receiver could not durably store the notification."
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "ApiResponse_HealthStatus_": {
        "properties": {
          "data": {
            "$ref": "#/components/schemas/HealthStatus"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "title": "ApiResponse[HealthStatus]"
      },
      "ApiResponse_PublicCallDetail_": {
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PublicCallDetail"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "title": "ApiResponse[PublicCallDetail]"
      },
      "ApiResponse_PublicCall_": {
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PublicCall"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "title": "ApiResponse[PublicCall]"
      },
      "ApiResponse_PublicScheduleConfig_": {
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PublicScheduleConfig"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "title": "ApiResponse[PublicScheduleConfig]"
      },
      "CallCompletedWebhookPayload": {
        "properties": {
          "type": {
            "type": "string",
            "const": "complete",
            "title": "Type",
            "default": "complete"
          },
          "call_started": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Call Started"
          },
          "response_body": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Response Body"
          },
          "call_data": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallData"
              },
              {
                "type": "null"
              }
            ]
          },
          "retry_attempt_number": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Retry Attempt Number",
            "default": 1
          },
          "root_call_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Root Call Id"
          },
          "next_retry_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Retry At"
          }
        },
        "type": "object",
        "required": [
          "response_body",
          "call_data",
          "type"
        ],
        "title": "CallCompletedWebhookPayload"
      },
      "CallData": {
        "properties": {
          "call_id": {
            "type": "string",
            "title": "Call Id"
          },
          "direction": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PhoneDirection"
              },
              {
                "type": "null"
              }
            ]
          },
          "phone_number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone Number"
          },
          "status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "total_duration": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Total Duration"
          },
          "enhanced_transcript": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Enhanced Transcript"
          },
          "report": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Report"
          },
          "recording_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Recording Url"
          },
          "permanent_recording_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Permanent Recording Url"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "started_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started At"
          },
          "ended_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ended At"
          },
          "interaction_type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/InteractionType"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "call_id",
          "created_at"
        ],
        "title": "CallData"
      },
      "CallLimitExceededPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "call_limit_exceeded",
            "title": "Type",
            "default": "call_limit_exceeded"
          },
          "phone_number": {
            "type": "string",
            "title": "Phone Number",
            "description": "The phone number that hit the limit."
          },
          "call_limit": {
            "type": "integer",
            "title": "Call Limit",
            "description": "The per-number call limit that was reached."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "phone_number",
          "call_limit",
          "type"
        ],
        "title": "CallLimitExceededPayload"
      },
      "CallNotCompletedType": {
        "type": "string",
        "enum": [
          "failed",
          "automation",
          "user_rejected",
          "user_unavailable",
          "no_answer",
          "rejected_by_carrier",
          "invalid_number",
          "sip_trunk_failure",
          "cancelled",
          "expired"
        ],
        "title": "CallNotCompletedType"
      },
      "CallNotCompletedWebhookPayload": {
        "properties": {
          "type": {
            "$ref": "#/components/schemas/CallNotCompletedType"
          },
          "retry_attempt_number": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Retry Attempt Number",
            "default": 1
          },
          "root_call_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Root Call Id"
          },
          "next_retry_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Retry At"
          }
        },
        "type": "object",
        "required": [
          "type"
        ],
        "title": "CallNotCompletedWebhookPayload"
      },
      "CallNotFoundPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "call_not_found",
            "title": "Type",
            "default": "call_not_found"
          },
          "call_id": {
            "type": "string",
            "title": "Call Id"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "call_id",
          "type"
        ],
        "title": "CallNotFoundPayload"
      },
      "CallNotPendingPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "call_not_pending",
            "title": "Type",
            "default": "call_not_pending"
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "status",
          "type"
        ],
        "title": "CallNotPendingPayload"
      },
      "CallStatus": {
        "type": "string",
        "enum": [
          "queued",
          "in_progress",
          "transferring",
          "transferred",
          "completed",
          "timeout",
          "user_rejected",
          "max_duration_reached",
          "failed",
          "ringing",
          "voicemail",
          "dialing",
          "cancelled",
          "expired",
          "scheduled"
        ],
        "title": "CallStatus"
      },
      "CreateCallRequest": {
        "properties": {
          "phone_number": {
            "type": "string",
            "title": "Phone Number",
            "description": "Phone number to call in E.164 format.",
            "examples": [
              "+966512345678"
            ]
          },
          "scenario_id": {
            "type": "string",
            "minLength": 1,
            "title": "Scenario Id",
            "description": "UUID of an active scenario copied from your Sarj.ai dashboard.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ]
          },
          "variables": {
            "additionalProperties": true,
            "type": "object",
            "title": "Variables",
            "description": "Template variables to inject into the scenario."
          },
          "language": {
            "$ref": "#/components/schemas/Language",
            "description": "Language for the call. Defaults to Arabic (ar) if not specified.",
            "default": "ar"
          },
          "scheduled_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scheduled At",
            "description": "Book the call for a future time (RFC 3339 with timezone offset) instead of dialing now. Must be at least 10 minutes ahead and within 30 days. The call is created with status 'scheduled' and dialed when due; cancel or reschedule it via the corresponding endpoints while pending."
          },
          "schedule_config_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Schedule Config Id",
            "description": "Retry rules for this call, referencing a config created via createScheduleConfig. Omitted, the scenario's default schedule config applies automatically when one is configured.",
            "examples": [
              "01890000-0000-7000-8000-000000000000"
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "phone_number",
          "scenario_id"
        ],
        "title": "CreateCallRequest"
      },
      "CreateScheduleConfigRequest": {
        "properties": {
          "enabled": {
            "type": "boolean",
            "title": "Enabled",
            "description": "Whether calls referencing this config retry. Disabling cancels pending retries at release time.",
            "default": true
          },
          "max_retries": {
            "type": "integer",
            "maximum": 10.0,
            "minimum": 1.0,
            "title": "Max Retries",
            "description": "Maximum additional dials after eligible unsuccessful outcomes (1-10). Two retries allow up to three total dials.",
            "examples": [
              2
            ]
          },
          "wait_between": {
            "type": "string",
            "format": "duration",
            "title": "Wait Between",
            "description": "Wait between attempts, measured from the end of the previous call. Use an ISO 8601 duration such as 'PT2H'. 60 seconds to 30 days.",
            "examples": [
              "PT2H"
            ]
          },
          "retry_window": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimeWindow"
              },
              {
                "type": "null"
              }
            ],
            "description": "Daily window retries may dial in, as start/end wall-clock edges in the config timezone. Omitted, retries dial at any hour."
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timezone",
            "description": "IANA timezone the retry_window is evaluated in. Omitted, the scenario's timezone applies.",
            "examples": [
              "Asia/Riyadh"
            ]
          },
          "expire_after": {
            "anyOf": [
              {
                "type": "string",
                "format": "duration"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expire After",
            "description": "Stop retrying this long after the first call ended, whichever of attempts/expiry hits first. 1 hour to 7 days (the platform ceiling); omitted, only max_retries and the ceiling bound the group.",
            "examples": [
              "P3D"
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "max_retries",
          "wait_between"
        ],
        "title": "CreateScheduleConfigRequest"
      },
      "ErrorResponse": {
        "properties": {
          "error": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ValidationErrorPayload"
              },
              {
                "$ref": "#/components/schemas/UnauthorizedPayload"
              },
              {
                "$ref": "#/components/schemas/NotFoundPayload"
              },
              {
                "$ref": "#/components/schemas/InternalErrorPayload"
              },
              {
                "$ref": "#/components/schemas/PhoneNumberBlockedPayload"
              },
              {
                "$ref": "#/components/schemas/CallLimitExceededPayload"
              },
              {
                "$ref": "#/components/schemas/OutboundCallFailedPayload"
              },
              {
                "$ref": "#/components/schemas/ScenarioNotFoundPayload"
              },
              {
                "$ref": "#/components/schemas/ScenarioForbiddenPayload"
              },
              {
                "$ref": "#/components/schemas/ScenarioInvalidConfigPayload"
              },
              {
                "$ref": "#/components/schemas/NoOrganizationPayload"
              },
              {
                "$ref": "#/components/schemas/CallNotFoundPayload"
              },
              {
                "$ref": "#/components/schemas/OauthInvalidRedirectUriPayload"
              },
              {
                "$ref": "#/components/schemas/SchedulingDisabledPayload"
              },
              {
                "$ref": "#/components/schemas/InvalidScheduleTimePayload"
              },
              {
                "$ref": "#/components/schemas/ScheduleConfigNotFoundPayload"
              },
              {
                "$ref": "#/components/schemas/CallNotPendingPayload"
              }
            ],
            "title": "Error",
            "discriminator": {
              "propertyName": "type",
              "mapping": {
                "call_limit_exceeded": "#/components/schemas/CallLimitExceededPayload",
                "call_not_found": "#/components/schemas/CallNotFoundPayload",
                "call_not_pending": "#/components/schemas/CallNotPendingPayload",
                "internal_error": "#/components/schemas/InternalErrorPayload",
                "invalid_schedule_time": "#/components/schemas/InvalidScheduleTimePayload",
                "no_organization": "#/components/schemas/NoOrganizationPayload",
                "not_found": "#/components/schemas/NotFoundPayload",
                "oauth_invalid_redirect_uri": "#/components/schemas/OauthInvalidRedirectUriPayload",
                "outbound_call_failed": "#/components/schemas/OutboundCallFailedPayload",
                "phone_number_blocked": "#/components/schemas/PhoneNumberBlockedPayload",
                "scenario_forbidden": "#/components/schemas/ScenarioForbiddenPayload",
                "scenario_invalid_config": "#/components/schemas/ScenarioInvalidConfigPayload",
                "scenario_not_found": "#/components/schemas/ScenarioNotFoundPayload",
                "schedule_config_not_found": "#/components/schemas/ScheduleConfigNotFoundPayload",
                "scheduling_disabled": "#/components/schemas/SchedulingDisabledPayload",
                "unauthorized": "#/components/schemas/UnauthorizedPayload",
                "validation_error": "#/components/schemas/ValidationErrorPayload"
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "error",
          "meta"
        ],
        "title": "ErrorResponse",
        "description": "Standard envelope for all public API error responses.\n\n`error` is a discriminated union — branch on `error.type` to access\nvariant-specific fields. Generated clients preserve unknown tagged variants\nso applications can log and surface newer server errors safely."
      },
      "FieldViolation": {
        "properties": {
          "field": {
            "type": "string",
            "title": "Field",
            "description": "Dot-separated path to the invalid field (e.g. `phone_number.country_code`).",
            "examples": [
              "phone_number.country_code"
            ]
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable description of the violation.",
            "examples": [
              "Must start with +"
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "field",
          "message"
        ],
        "title": "FieldViolation"
      },
      "HealthStatus": {
        "properties": {
          "status": {
            "type": "string",
            "const": "ok",
            "title": "Status",
            "description": "Health status of the API. Always `ok` when this endpoint responds successfully.",
            "examples": [
              "ok"
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "status"
        ],
        "title": "HealthStatus",
        "description": "Represents the operational status of the API."
      },
      "InteractionType": {
        "type": "string",
        "enum": [
          "voice",
          "chat"
        ],
        "title": "InteractionType"
      },
      "InternalErrorPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "internal_error",
            "title": "Type",
            "default": "internal_error"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "InternalErrorPayload"
      },
      "InvalidScheduleTimePayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "invalid_schedule_time",
            "title": "Type",
            "default": "invalid_schedule_time"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "InvalidScheduleTimePayload"
      },
      "Language": {
        "type": "string",
        "enum": [
          "en",
          "ar",
          "ur"
        ],
        "title": "Language",
        "description": "Language codes in ISO 639-1"
      },
      "NoOrganizationPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "no_organization",
            "title": "Type",
            "default": "no_organization"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "NoOrganizationPayload"
      },
      "NotFoundPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "not_found",
            "title": "Type",
            "default": "not_found"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "NotFoundPayload"
      },
      "OauthInvalidRedirectUriPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "oauth_invalid_redirect_uri",
            "title": "Type",
            "default": "oauth_invalid_redirect_uri"
          },
          "redirect_uri": {
            "type": "string",
            "title": "Redirect Uri"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "redirect_uri",
          "type"
        ],
        "title": "OauthInvalidRedirectUriPayload"
      },
      "OutboundCallFailedPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "outbound_call_failed",
            "title": "Type",
            "default": "outbound_call_failed"
          },
          "reason": {
            "type": "string",
            "title": "Reason",
            "description": "Stable-ish reason string from the call service."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "reason",
          "type"
        ],
        "title": "OutboundCallFailedPayload"
      },
      "PhoneDirection": {
        "type": "string",
        "enum": [
          "inbound",
          "outbound"
        ],
        "title": "PhoneDirection"
      },
      "PhoneNumberBlockedPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "phone_number_blocked",
            "title": "Type",
            "default": "phone_number_blocked"
          },
          "phone_number": {
            "type": "string",
            "title": "Phone Number",
            "description": "The phone number that is blocked."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "phone_number",
          "type"
        ],
        "title": "PhoneNumberBlockedPayload"
      },
      "PublicCall": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id",
            "description": "Unique call identifier.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/CallStatus",
            "description": "Current call status.",
            "examples": [
              "queued"
            ]
          },
          "phone_number": {
            "type": "string",
            "title": "Phone Number",
            "description": "Destination phone number."
          },
          "scenario_id": {
            "type": "string",
            "title": "Scenario Id",
            "description": "Scenario used for this call."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Timestamp when the call was created. For a scheduled call this is booking time, not dial time."
          },
          "scheduled_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scheduled At",
            "description": "When a scheduled call is booked to dial. Null for immediate calls."
          },
          "booking_outcome": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "created",
                  "rescheduled_existing",
                  "kept_existing"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Booking Outcome",
            "description": "Only present on the createCall booking (202) response. 'created' is a new booking; 'rescheduled_existing' or 'kept_existing' means an existing pending call for the same scenario+phone was taken over or kept, and the returned id/scheduled_at are that existing call's — check it rather than assuming a second call now exists."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "id",
          "status",
          "phone_number",
          "scenario_id",
          "created_at"
        ],
        "title": "PublicCall"
      },
      "PublicCallDetail": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id",
            "description": "Unique call identifier.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ]
          },
          "status": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "Current call status.",
            "examples": [
              "in_progress"
            ]
          },
          "phone_number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone Number",
            "description": "Destination phone number."
          },
          "scenario_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scenario Id",
            "description": "Scenario used for this call."
          },
          "language": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Language"
              },
              {
                "type": "null"
              }
            ],
            "description": "Language the call was conducted in."
          },
          "direction": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PhoneDirection"
              },
              {
                "type": "null"
              }
            ],
            "description": "Call direction (inbound or outbound)."
          },
          "variables": {
            "additionalProperties": true,
            "type": "object",
            "title": "Variables",
            "description": "Template variables that were injected into the scenario. Defaults to an empty object for non-custom scenarios."
          },
          "duration": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Duration",
            "description": "Total call duration in seconds. Null until the call ends."
          },
          "recording_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Recording Url",
            "description": "DEPRECATED — use `permanent_recording_url`. Short-lived signed URL to download the call recording; it expires 24 hours after issue once the recording-endpoint rollout is enabled on this deployment (7 days before then). Null when the call has not ended or no recording exists.",
            "deprecated": true
          },
          "permanent_recording_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Permanent Recording Url",
            "description": "Stable Sarj URL to download the call recording, authenticated with your API key (same credential as this endpoint). Unlike `recording_url` it does not expire, so it is safe to store. Null when the call has not ended, no recording exists, or the recording-endpoint rollout is not yet enabled on this deployment — during rollout, fall back to `recording_url`."
          },
          "transcript": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/PublicTranscriptMessage"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Transcript",
            "description": "Conversation transcript as an ordered list of spoken messages. Null until the call ends."
          },
          "report": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicCallReport"
              },
              {
                "type": "null"
              }
            ],
            "description": "Post-call outcome report with success-criteria results. Generated asynchronously, typically within a couple of minutes after the call ends, so it is null immediately after completion — poll again or use the call-completed webhook. Only calls that end with status 'completed' or 'max_duration_reached' receive a report; for every other terminal status this stays null. In rare failure cases a completed call never receives a report — treat the webhook as authoritative and bound your polling."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Timestamp when the call was created. For a scheduled call this is booking time, not dial time."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "title": "Updated At",
            "description": "Timestamp of the most recent update to the call."
          },
          "started_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started At",
            "description": "Timestamp when the call was answered."
          },
          "ended_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ended At",
            "description": "Timestamp when the call ended."
          },
          "scheduled_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scheduled At",
            "description": "When this call was (or is) booked to dial. Set on scheduled calls and platform-booked retry attempts; null for immediate calls."
          },
          "is_retry": {
            "type": "boolean",
            "title": "Is Retry",
            "description": "Whether the platform placed this call as a retry of an earlier attempt.",
            "default": false
          },
          "retry_attempt_number": {
            "type": "integer",
            "title": "Retry Attempt Number",
            "description": "Which dial this call is within its retry group (1 for the first attempt).",
            "default": 1
          },
          "root_call_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Root Call Id",
            "description": "Identifier of the retry group's first attempt, shared by every attempt in the group. Null when this call is the first (or only) attempt."
          },
          "next_retry_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Retry At",
            "description": "When the group's next attempt is booked to dial, if one is pending. Null means no further attempt is planned."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "id",
          "created_at",
          "updated_at"
        ],
        "title": "PublicCallDetail"
      },
      "PublicCallOutcome": {
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "failure",
              "error"
            ],
            "title": "Status",
            "description": "Overall call outcome assessed from the transcript: 'success' or 'failure' against the scenario's success criteria, 'error' when the call could not be assessed.",
            "examples": [
              "success"
            ]
          },
          "reason": {
            "type": "string",
            "title": "Reason",
            "description": "Explanation of why the call received this outcome."
          },
          "success_criteria": {
            "items": {
              "$ref": "#/components/schemas/PublicSuccessCriteriaResult"
            },
            "type": "array",
            "title": "Success Criteria",
            "description": "Result for each success criterion configured on the scenario."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "status",
          "reason",
          "success_criteria"
        ],
        "title": "PublicCallOutcome"
      },
      "PublicCallReport": {
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time",
            "title": "Generated At",
            "description": "Timestamp when the report was generated."
          },
          "outcome": {
            "$ref": "#/components/schemas/PublicCallOutcome",
            "description": "Outcome assessment of the call."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "generated_at",
          "outcome"
        ],
        "title": "PublicCallReport"
      },
      "PublicScheduleConfig": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id",
            "description": "Unique schedule config identifier. Pass it as schedule_config_id when creating calls.",
            "examples": [
              "01890000-0000-7000-8000-000000000000"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Timestamp when the config was created."
          },
          "enabled": {
            "type": "boolean",
            "title": "Enabled",
            "description": "Whether calls referencing this config retry."
          },
          "max_retries": {
            "type": "integer",
            "title": "Max Retries",
            "description": "Maximum additional dials after eligible unsuccessful outcomes."
          },
          "wait_between": {
            "type": "string",
            "format": "duration",
            "title": "Wait Between",
            "description": "Wait between attempts (ISO 8601 duration)."
          },
          "retry_window": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TimeWindow"
              },
              {
                "type": "null"
              }
            ],
            "description": "Daily window retries may dial in."
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timezone",
            "description": "IANA timezone the retry_window is evaluated in."
          },
          "expire_after": {
            "anyOf": [
              {
                "type": "string",
                "format": "duration"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expire After",
            "description": "How long after the first call's end the group stops retrying."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "id",
          "created_at",
          "enabled",
          "max_retries",
          "wait_between"
        ],
        "title": "PublicScheduleConfig"
      },
      "PublicSuccessCriteriaResult": {
        "properties": {
          "text": {
            "type": "string",
            "title": "Text",
            "description": "The success criterion, as configured on the scenario."
          },
          "achieved": {
            "type": "boolean",
            "title": "Achieved",
            "description": "Whether the call met this criterion."
          },
          "is_primary": {
            "type": "boolean",
            "title": "Is Primary",
            "description": "Whether this is the scenario's primary success criterion."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "text",
          "achieved",
          "is_primary"
        ],
        "title": "PublicSuccessCriteriaResult"
      },
      "PublicTranscriptMessage": {
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ],
            "title": "Role",
            "description": "Speaker role: 'user' for the called party, 'assistant' for the AI agent."
          },
          "content": {
            "type": "string",
            "title": "Content",
            "description": "Text of the spoken message."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "title": "PublicTranscriptMessage"
      },
      "RescheduleCallRequest": {
        "properties": {
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "title": "Scheduled At",
            "description": "New dial time (RFC 3339 with timezone offset). Must be at least 10 minutes ahead and within 30 days."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "scheduled_at"
        ],
        "title": "RescheduleCallRequest"
      },
      "ResponseMeta": {
        "properties": {
          "request_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Request Id",
            "description": "Unique identifier for this request, mirrored in the `X-Request-ID` response header. Supply your own via the `X-Request-ID` request header (max 128 chars, ASCII printable); otherwise the server generates a UUID v4. Include in support tickets.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "ResponseMeta",
        "description": "Contains request-level metadata included with every public API response."
      },
      "ScenarioForbiddenPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "scenario_forbidden",
            "title": "Type",
            "default": "scenario_forbidden"
          },
          "scenario_id": {
            "type": "string",
            "title": "Scenario Id"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "scenario_id",
          "type"
        ],
        "title": "ScenarioForbiddenPayload"
      },
      "ScenarioInvalidConfigPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "scenario_invalid_config",
            "title": "Type",
            "default": "scenario_invalid_config"
          },
          "scenario_id": {
            "type": "string",
            "title": "Scenario Id"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "scenario_id",
          "type"
        ],
        "title": "ScenarioInvalidConfigPayload"
      },
      "ScenarioNotFoundPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "scenario_not_found",
            "title": "Type",
            "default": "scenario_not_found"
          },
          "scenario_id": {
            "type": "string",
            "title": "Scenario Id"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "scenario_id",
          "type"
        ],
        "title": "ScenarioNotFoundPayload"
      },
      "ScheduleConfigNotFoundPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "schedule_config_not_found",
            "title": "Type",
            "default": "schedule_config_not_found"
          },
          "schedule_config_id": {
            "type": "string",
            "title": "Schedule Config Id"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "schedule_config_id",
          "type"
        ],
        "title": "ScheduleConfigNotFoundPayload"
      },
      "SchedulingDisabledPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "scheduling_disabled",
            "title": "Type",
            "default": "scheduling_disabled"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "SchedulingDisabledPayload"
      },
      "TimeEdge": {
        "properties": {
          "hour": {
            "type": "integer",
            "maximum": 23.0,
            "minimum": 0.0,
            "title": "Hour"
          },
          "minute": {
            "type": "integer",
            "maximum": 59.0,
            "minimum": 0.0,
            "title": "Minute"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "hour",
          "minute"
        ],
        "title": "TimeEdge"
      },
      "TimeWindow": {
        "properties": {
          "start_edge": {
            "$ref": "#/components/schemas/TimeEdge"
          },
          "end_edge": {
            "$ref": "#/components/schemas/TimeEdge"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "start_edge",
          "end_edge"
        ],
        "title": "TimeWindow"
      },
      "UnauthorizedPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "unauthorized",
            "title": "Type",
            "default": "unauthorized"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "type"
        ],
        "title": "UnauthorizedPayload"
      },
      "ValidationErrorPayload": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable summary. Do not parse programmatically — branch on `type`."
          },
          "type": {
            "type": "string",
            "const": "validation_error",
            "title": "Type",
            "default": "validation_error"
          },
          "field_violations": {
            "items": {
              "$ref": "#/components/schemas/FieldViolation"
            },
            "type": "array",
            "title": "Field Violations",
            "description": "Per-field validation details."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "message",
          "field_violations",
          "type"
        ],
        "title": "ValidationErrorPayload"
      },
      "WebhookNotification": {
        "properties": {
          "call_id": {
            "type": "string",
            "title": "Call Id"
          },
          "payload": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallCompletedWebhookPayload"
              },
              {
                "$ref": "#/components/schemas/CallNotCompletedWebhookPayload"
              }
            ],
            "title": "Payload"
          }
        },
        "type": "object",
        "required": [
          "call_id",
          "payload"
        ],
        "title": "WebhookNotification",
        "x-speakeasy-include": true
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from your Sarj.ai dashboard. Pass as: Authorization: Bearer <api-key>"
      }
    }
  },
  "tags": [
    {
      "name": "System",
      "description": "Service health and status."
    },
    {
      "name": "Calls",
      "description": "Create outbound voice calls and retrieve call details and transcripts."
    },
    {
      "name": "Schedule Configs",
      "description": "Create and manage retry policies for scheduled outbound calls."
    },
    {
      "name": "Webhooks",
      "description": "Receive call outcomes at your configured endpoint."
    }
  ],
  "externalDocs": {
    "description": "Sarj.ai Documentation",
    "url": "https://platform-docs.sarj.ai"
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ]
}
