API reference

Base URL /v1. Auth: Authorization: Bearer eppie_agent_sk_… or an OAuth access token. Every operation is also an MCP tool at /mcp (except sign-in and API-key management). Machine-readable: /openapi.json.

POST /v1/auth/email-code

Email a 6-digit sign-in code. Works for new and existing accounts. Read the code from the inbox (or ask your human), then call POST /v1/auth/verify.

Tool: (REST only) · no auth

POST /v1/auth/verify

Exchange the emailed code for an API key (eppie_agent_sk_…). Creates the account on first use. The key is shown once.

Tool: (REST only) · no auth

GET /v1/account

Get the signed-in account: email, trust tier and current limits.

Tool: get_account

GET /v1/api-keys

List active API keys (name and prefix only; full keys are shown once at creation).

Tool: (REST only)

DELETE /v1/api-keys/{id}

Revoke an API key. Requests with it fail with 401 from then on.

Tool: (REST only)

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

GET /v1/wallet

Get the prepaid balance (USD), trust tier and limits. Calls cost $0.12/min (per second); numbers $3.00/month.

Tool: get_wallet

GET /v1/wallet/ledger

List wallet movements (top-ups, call charges, number rentals, refunds), newest first.

Tool: list_ledger

[
  {
    "name": "cursor",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string"
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "schema": {
      "default": 20,
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  }
]

POST /v1/wallet/topups

Add $10–$500 of credit. With shared_payment_token (a Stripe shared payment token from your wallet, e.g. Link) the charge happens now; without it you get a checkout_url to pay, then poll GET /v1/wallet/topups/{id}.

Tool: top_up · Idempotency-Key

[
  {
    "name": "Idempotency-Key",
    "in": "header",
    "required": false,
    "description": "Retry-safe key (≤ 200 chars). Same key + same body returns the original response.",
    "schema": {
      "type": "string",
      "maxLength": 200
    }
  }
]

GET /v1/wallet/topups/{id}

Get a top-up: pending (checkout not paid yet), succeeded, failed or expired.

Tool: get_top_up

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

GET /v1/voices

List the gpt-live-1 voices an agent can use, each with a short audio sample URL.

Tool: list_voices · no auth

POST /v1/agents

Create a voice agent: what it should do on calls (instructions), an optional first line (opener), a voice and a max call length. Callers always hear a fixed AI disclosure first. instructions and opener can use {{caller_number}} (the other party), {{local_time}}, {{agent_name}} and {{direction}} (inbound or outbound); batch targets can add their own {{variables}}. The opener is used on calls the agent answers; batches set their own.

Tool: create_agent

GET /v1/agents

List your voice agents.

Tool: list_agents

GET /v1/agents/{id}

Get one voice agent.

Tool: get_agent

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

PATCH /v1/agents/{id}

Change any of an agent's fields. Calls already in progress keep the old settings. instructions and opener can use {{caller_number}} (the other party), {{local_time}}, {{agent_name}} and {{direction}} (inbound or outbound); batch targets can add their own {{variables}}. The opener is used on calls the agent answers; batches set their own.

Tool: update_agent

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

DELETE /v1/agents/{id}

Delete a voice agent. Numbers assigned to it stop answering until you assign another agent, and its unfinished batches are canceled.

Tool: delete_agent

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

GET /v1/numbers/available

Search US local phone numbers you can buy, optionally by area code or digits they contain.

Tool: search_numbers

[
  {
    "name": "area_code",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "pattern": "^[2-9]\\d{2}$"
    }
  },
  {
    "name": "contains",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "pattern": "^[0-9*]{1,10}$"
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "schema": {
      "default": 10,
      "type": "integer",
      "minimum": 1,
      "maximum": 20
    }
  }
]

POST /v1/numbers

Buy a US number for $3.00/month, charged now from the wallet and monthly after. Pass agent_id to have that agent answer its calls. Not refunded when released.

Tool: buy_number · Idempotency-Key

[
  {
    "name": "Idempotency-Key",
    "in": "header",
    "required": false,
    "description": "Retry-safe key (≤ 200 chars). Same key + same body returns the original response.",
    "schema": {
      "type": "string",
      "maxLength": 200
    }
  }
]

GET /v1/numbers

List your numbers (active and in grace), with the agent that answers each.

Tool: list_numbers

GET /v1/numbers/{id}

Get one number, including its renewal date and grace status.

Tool: get_number

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

PATCH /v1/numbers/{id}

Assign the agent that answers this number (null = stop answering), or change its label.

Tool: update_number

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

DELETE /v1/numbers/{id}

Release a number back to the carrier. Immediate; the current month is not refunded.

Tool: release_number

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

GET /v1/calls

List calls, newest first: inbound calls to your numbers (including rejected ones; reject_reason says why) and outbound calls from batches. Filter by direction, batch, agent, number or status.

Tool: list_calls

[
  {
    "name": "direction",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "enum": [
        "inbound",
        "outbound"
      ]
    }
  },
  {
    "name": "batch_id",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  {
    "name": "agent_id",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  {
    "name": "number_id",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  {
    "name": "status",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "enum": [
        "queued",
        "dialing",
        "pending",
        "in_progress",
        "completed",
        "failed",
        "rejected",
        "no_answer",
        "busy",
        "machine",
        "canceled",
        "skipped_dnc"
      ]
    }
  },
  {
    "name": "cursor",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string"
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "schema": {
      "default": 20,
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  }
]

GET /v1/calls/{id}

Get one call: status, how it ended, duration, cost, the transcript (null until it ends) and, for batch calls, the extracted outcome.

Tool: get_call

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

POST /v1/batches

Call up to 1,000 US numbers with one of your voice agents (agent_id). Each target can carry variables for {{name}}-style fields in the agent's instructions; context adds a note for this batch, opener is said right after the AI disclosure, outcome_schema (JSON Schema) gets a structured result per call. Every number must have agreed to be called: say how in consent.source. Calls go out 08:00–21:00 recipient time, skip your do-not-call list and hang up on voicemail. $0.12/min, charged per second, only for calls a person answers. Follow with GET /v1/batches/{id} and GET /v1/calls?batch_id=, or pass webhook_url.

Tool: create_batch · Idempotency-Key

[
  {
    "name": "Idempotency-Key",
    "in": "header",
    "required": false,
    "description": "Retry-safe key (≤ 200 chars). Same key + same body returns the original response.",
    "schema": {
      "type": "string",
      "maxLength": 200
    }
  }
]

POST /v1/calls

Call one US number with one of your voice agents (agent_id); variables fill {{name}}-style fields in its instructions. Every number must have agreed to be called: say how in consent.source. Calls go out 08:00–21:00 recipient time, skip your do-not-call list and hang up on voicemail. $0.12/min, charged per second, only for calls a person answers. Follow with GET /v1/calls/{id}.

Tool: place_call · Idempotency-Key

[
  {
    "name": "Idempotency-Key",
    "in": "header",
    "required": false,
    "description": "Retry-safe key (≤ 200 chars). Same key + same body returns the original response.",
    "schema": {
      "type": "string",
      "maxLength": 200
    }
  }
]

GET /v1/batches

List your batches, newest first, each with counts of its calls by status.

Tool: list_batches

[
  {
    "name": "status",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string",
      "enum": [
        "running",
        "paused",
        "completed",
        "canceled"
      ]
    }
  },
  {
    "name": "cursor",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string"
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "schema": {
      "default": 20,
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  }
]

GET /v1/batches/{id}

Get a batch: status (running, paused, completed, canceled), paused_reason and counts of its calls by status. Per-call results: GET /v1/calls?batch_id=.

Tool: get_batch

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

POST /v1/batches/{id}/pause

Pause a batch. Calls already ringing or connected finish; queued calls wait.

Tool: pause_batch

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

POST /v1/batches/{id}/resume

Resume a paused batch. A batch paused for a low balance resumes by itself after a top-up.

Tool: resume_batch

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

POST /v1/batches/{id}/cancel

Cancel a batch: its queued calls are canceled; calls already ringing or connected finish.

Tool: cancel_batch

[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string"
    }
  }
]

GET /v1/do-not-call

List the numbers this account never calls. source is api (you added it) or recipient_opt_out (the person asked on a call; permanent).

Tool: list_do_not_call

[
  {
    "name": "cursor",
    "in": "query",
    "required": false,
    "schema": {
      "type": "string"
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "schema": {
      "default": 50,
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  }
]

POST /v1/do-not-call

Add up to 1,000 US numbers this account must never call. Queued calls to them are skipped (status skipped_dnc).

Tool: add_do_not_call

DELETE /v1/do-not-call/{e164}

Remove a number you added to the do-not-call list. Numbers whose owner asked not to be called stay.

Tool: remove_do_not_call

[
  {
    "name": "e164",
    "in": "path",
    "required": true,
    "schema": {
      "type": "string",
      "pattern": "^\\+1[2-9]\\d{9}$"
    }
  }
]

GET /v1/webhook-secret

Get the secret that signs your webhooks. Each delivery has x-eppie-signature: t={unix},v1={hex HMAC-SHA256(secret, t + "." + raw body)}.

Tool: get_webhook_secret

POST /v1/webhook-secret/rotate

Replace the webhook signing secret. Deliveries are signed with the new one from now on.

Tool: rotate_webhook_secret