{
  "openapi": "3.1.0",
  "info": {
    "title": "JustCallMe",
    "version": "1.0.0",
    "description": "Phone your human from a coding agent. Create a call with a question, poll for the confirmed answer, acknowledge it. API keys are created by the account holder at https://justcallme.dev/app; agents cannot sign up."
  },
  "servers": [{"url": "https://justcallme.dev"}],
  "security": [{"apiKey": []}],
  "paths": {
    "/api/calls": {
      "post": {
        "operationId": "createCall",
        "summary": "Ask the human a question by phone",
        "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "pattern": "^[\\w-]{8,100}$"}, "description": "A UUID. Reuse it when retrying the same question so the phone does not ring twice."}],
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CallRequest"}}}},
        "responses": {
          "201": {"description": "Call created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Call"}}}},
          "400": {"$ref": "#/components/responses/Error"},
          "401": {"$ref": "#/components/responses/Error"},
          "402": {"description": "Under 2 minutes of balance", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "409": {"description": "A call is already open, or the idempotency key was used for a different question", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "429": {"$ref": "#/components/responses/Error"}
        }
      }
    },
    "/api/calls/{id}": {
      "get": {
        "operationId": "getCall",
        "summary": "Poll a call every 3 seconds for up to 180 seconds",
        "parameters": [{"$ref": "#/components/parameters/CallId"}],
        "responses": {
          "200": {"description": "The call record", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Call"}}}},
          "401": {"$ref": "#/components/responses/Error"},
          "404": {"$ref": "#/components/responses/Error"}
        }
      }
    },
    "/api/calls/{id}/ack": {
      "post": {
        "operationId": "acknowledgeCall",
        "summary": "Acknowledge an answered call",
        "parameters": [{"$ref": "#/components/parameters/CallId"}],
        "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}},
        "responses": {
          "200": {"description": "The acknowledged call", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Call"}}}},
          "409": {"description": "The call is not answered", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      }
    },
    "/api/calls/{id}/cancel": {
      "post": {
        "operationId": "cancelCall",
        "summary": "Cancel a waiting call and hang up",
        "parameters": [{"$ref": "#/components/parameters/CallId"}],
        "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}},
        "responses": {"200": {"description": "The cancelled call", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Call"}}}}}
      }
    },
    "/api/me": {
      "get": {
        "operationId": "getAccount",
        "summary": "Remaining minutes and recent calls",
        "responses": {"200": {"description": "Account status", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Account"}}}}}
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {"type": "http", "scheme": "bearer", "description": "Account API key, created by the human at https://justcallme.dev/app. Read it from JUSTCALLME_API_KEY."}
    },
    "parameters": {
      "CallId": {"name": "id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}
    },
    "responses": {
      "Error": {"description": "Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
    },
    "schemas": {
      "Error": {"type": "object", "properties": {"error": {"type": "string"}}, "required": ["error"]},
      "CallRequest": {
        "type": "object",
        "required": ["question"],
        "properties": {
          "question": {"type": "string", "minLength": 3, "maxLength": 1200},
          "context": {"type": "string", "maxLength": 8000, "description": "Background for the voice agent. No secrets."},
          "choices": {"type": "array", "maxItems": 8, "items": {"type": "string", "maxLength": 200}}
        }
      },
      "Call": {
        "type": "object",
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "question": {"type": "string"},
          "context": {"type": "string"},
          "choices": {"type": "array", "items": {"type": "string"}},
          "status": {"type": "string", "enum": ["waiting", "answered", "failed", "expired", "cancelled"]},
          "answer": {"type": ["string", "null"], "description": "Set only when status is answered. One of choices when choices were given."},
          "transcript": {"type": ["string", "null"], "description": "The two-sided conversation, written when the call ends."},
          "error": {"type": ["string", "null"]},
          "createdAt": {"type": "string", "format": "date-time"},
          "connectedSeconds": {"type": "integer"},
          "creditsCharged": {"type": "integer", "description": "One credit is ten connected seconds."},
          "acknowledged": {"type": "boolean"}
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "name": {"type": "string"},
          "phone": {"type": "string"},
          "credits": {"type": "integer"},
          "minutes": {"type": "number"},
          "subscription": {"type": "string"},
          "keyCount": {"type": "integer"},
          "calls": {"type": "array", "items": {"$ref": "#/components/schemas/Call"}}
        }
      }
    }
  }
}
