veevo.ai

Agent Skill Reference

Copy this SKILL.md into your AI agent or Claude Code skill to give it full context on the Veevo API.

Usage

Drop this file as SKILL.md into your project or agent configuration. Any AI agent with this context will know how to register accounts, configure phone numbers, implement callbacks, and manage billing through the Veevo API.

SKILL.md

---
name: Veevo Management API
description: This skill should be used when the user asks to "register for Veevo", "get an API key", "add a phone number", "check usage", "upgrade my plan", "manage my subscription", "set up billing", "register a callback URL", "how many minutes do I have left", "build a voice agent", "integrate with Veevo", or needs to build a system that uses the Veevo real-time voice engine.
---

# Veevo Management API

The Veevo Management API provides developer access to the Veevo real-time voice engine. Register an account, configure phone numbers with callback URLs, track usage, and manage billing — all through a REST API.

## Prerequisites

To use Veevo, the developer needs:

- **A Twilio account** with at least one phone number that has voice capability
- **Twilio credentials** (`twilioAccountSid` and `twilioAuthToken`) from the Twilio console
- **An OpenAI API key** with access to Realtime API models (`gpt-realtime-1.5` or `gpt-realtime-mini`)
- **A backend server** that implements three callback endpoints (see Callback Implementation below)

The developer's Twilio and OpenAI credentials are never stored by Veevo. They are returned by the developer's backend on each call via the `onCallStart` callback.

## Getting Started

### 1. Create an Account

```bash
curl -X POST https://veevo.ai/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com", "password": "your_password", "companyName": "Your Company"}'
```

Response:

```json
{
  "account": { "id": "...", "email": "you@company.com", "status": "INACTIVE" },
  "apiKey": "rtk_abc123...",
  "token": "eyJhbG...",
  "checkoutUrl": "https://checkout.stripe.com/c/pay/...",
  "engineVoiceUrl": "https://engine.veevo.ai/voice"
}
```

Account starts as `INACTIVE`. Open `checkoutUrl` in a browser to complete the $5/mo payment via Stripe Checkout. Once payment succeeds, the account activates automatically. Store the `apiKey` securely — it is shown only once.

### 2. Register a Phone Number

```bash
curl -X POST https://veevo.ai/api/phone-numbers \
  -H "Authorization: Bearer rtk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+15551234567",
    "onCallStartUrl": "https://your-backend.com/calls/start",
    "onCallEndUrl": "https://your-backend.com/calls/end"
  }'
```

### 3. Point Twilio at the Engine

In the Twilio account dashboard, navigate to Phone Numbers → Manage → Active Numbers → select the number → Voice Configuration. Under "A call comes in", set the webhook URL to the `engineVoiceUrl` returned during registration and select HTTP POST.

### 4. Implement Callbacks

Build three endpoints on the developer's backend. The engine calls these during each call.

## Callback Implementation

### POST onCallStart

Called when a call comes in, before the conversation starts. Return the full call configuration.

**Engine sends:**
```json
{
  "callSid": "CA...",
  "callerPhone": "+19805551234",
  "calledNumber": "+15551234567",
  "timestamp": "2026-04-03T..."
}
```

**Developer returns:**
```json
{
  "twilioAccountSid": "AC...",
  "twilioAuthToken": "...",
  "openaiApiKey": "sk-...",
  "systemPrompt": "You are a helpful assistant for...",
  "voice": "marin",
  "model": "gpt-realtime-1.5",
  "vadEagerness": "high",
  "noiseReduction": "near_field",
  "inactivityTimeoutMs": 60000,
  "greetingUrl": "https://cdn.example.com/greeting.mp3",
  "onToolCallUrl": "https://your-backend.com/calls/tool",
  "tools": [],
  "metadata": { "customerId": "cust_123" }
}
```

| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `twilioAccountSid` | yes | — | Twilio account SID (starts with AC) |
| `twilioAuthToken` | yes | — | Twilio auth token for signature validation and call control |
| `openaiApiKey` | yes | — | OpenAI API key with Realtime API access |
| `systemPrompt` | yes | — | The AI agent's instructions, persona, and knowledge |
| `voice` | no | `marin` | OpenAI voice: alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar |
| `model` | no | `gpt-realtime-1.5` | `gpt-realtime-1.5` (best quality) or `gpt-realtime-mini` (cost-efficient) |
| `vadEagerness` | no | `high` | Voice activity detection sensitivity: low, medium, high, auto |
| `noiseReduction` | no | `near_field` | Noise reduction: near_field or far_field |
| `inactivityTimeoutMs` | no | `60000` | Auto-hangup after this many ms of silence |
| `greetingUrl` | no | — | URL to an MP3 played before the AI connects |
| `onToolCallUrl` | yes | — | Where the engine POSTs tool calls |
| `tools` | no | `[]` | OpenAI function definitions for the AI to invoke |
| `metadata` | no | — | Opaque data passed through to onToolCall and onCallEnd |

**Timeout:** 10 seconds. If the backend doesn't respond, the call is rejected.

### POST onToolCall

Called when the AI invokes a tool defined in the `tools` array.

**Engine sends:**
```json
{
  "toolName": "check_availability",
  "arguments": { "date": "April 15, 2026" },
  "metadata": { "customerId": "cust_123" },
  "timestamp": "2026-04-03T..."
}
```

**Developer returns:**
```json
{
  "result": "We have 3 standard units available for April 15."
}
```

**Timeout:** 30 seconds. The AI is blocked during this time — keep responses fast (<500ms) for natural conversation flow.

### POST onCallEnd

Called after the call ends. Contains the full transcript and cost breakdown.

```json
{
  "callSid": "CA...",
  "transcript": [
    { "role": "assistant", "content": "Hi, how can I help?", "timestamp": 1711900000 },
    { "role": "user", "content": "I need two units delivered Friday.", "timestamp": 1711900005 }
  ],
  "costBreakdown": {
    "inputTextTokens": 365,
    "inputAudioTokens": 90,
    "outputTextTokens": 45,
    "outputAudioTokens": 109,
    "costInputText": 0.00146,
    "costInputAudio": 0.00288,
    "costOutputText": 0.00072,
    "costOutputAudio": 0.006976,
    "costTwilio": 0.006647,
    "totalCost": 0.018483
  },
  "durationSeconds": 47,
  "metadata": { "customerId": "cust_123" },
  "timestamp": "2026-04-03T..."
}
```

## Pricing

- **$5/mo** platform access (card required on signup via Stripe Checkout)
- **$0.05/min** usage (metered, billed automatically at end of billing period)
- Unlimited phone numbers
- No tiers, no included minutes — pure pay-as-you-go after the base fee

Note: these are Veevo platform fees only. The developer pays OpenAI and Twilio directly through their own accounts.

## API Reference

All endpoints require `Authorization: Bearer rtk_...` unless noted.

### Authentication

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/auth/register` | Create account. Body: `{ email, password, companyName? }` |
| POST | `/api/auth/login` | Get JWT. Body: `{ email, password }` |

### Account & API Keys

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/account` | Account details, subscription, resource counts |
| POST | `/api/api-keys` | Create additional key. Body: `{ label? }` |
| GET | `/api/api-keys` | List active keys (prefixes only) |
| DELETE | `/api/api-keys/:id` | Revoke a key |

### Phone Numbers

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/phone-numbers` | Register. Body: `{ phoneNumber, onCallStartUrl, onCallEndUrl, onErrorUrl? }` |
| GET | `/api/phone-numbers` | List registered numbers |
| DELETE | `/api/phone-numbers/:id` | Remove a number |

Phone number must be a US number in E.164 format (`+1XXXXXXXXXX`). Unlimited numbers.

### Usage

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/usage` | Call history. Query: `limit` (max 100), `offset` |
| GET | `/api/usage/current` | Current month and all-time usage summary |

### Billing

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/billing/portal` | Get Stripe Billing Portal URL |
| GET | `/api/billing/subscription` | Current status, pricing, usage totals |

## Error Codes

| Code | Meaning |
|------|---------|
| 400 | Invalid request body or callback URL |
| 401 | Missing or invalid API key / credentials |
| 402 | Payment required — complete Stripe Checkout to activate account |
| 403 | Phone number not owned by account (outbound) |
| 404 | Resource not found |
| 409 | Phone number already registered by another account |
| 500 | Internal server error |

## Example Integration

A minimal Node.js backend implementing all three callbacks:

```javascript
const express = require('express');
const app = express();
app.use(express.json());

app.post('/calls/start', (req, res) => {
  res.json({
    twilioAccountSid: process.env.TWILIO_ACCOUNT_SID,
    twilioAuthToken: process.env.TWILIO_AUTH_TOKEN,
    openaiApiKey: process.env.OPENAI_API_KEY,
    systemPrompt: 'You are a helpful assistant. Greet the caller warmly.',
    voice: 'marin',
    onToolCallUrl: `${process.env.BASE_URL}/calls/tool`,
    tools: [],
  });
});

app.post('/calls/tool', (req, res) => {
  res.json({ result: `Handled ${req.body.toolName}` });
});

app.post('/calls/end', (req, res) => {
  console.log(`Call ${req.body.callSid} ended — ${req.body.durationSeconds}s`);
  console.log(`Cost: $${req.body.costBreakdown?.totalCost}`);
  res.json({ received: true });
});

app.listen(3001);
```