Manage Vapi resources via Git using YAML/Markdown as the source-of-truth.
| Dashboard / Ad-hoc API | GitOps | |
|---|---|---|
| History | Limited visibility of who changed what | Full git history with blame |
| Review | Changes go live immediately (can break things) | PR review before deploy |
| Rollback | Manual recreation | git revert + apply |
| Environments | Tedious to copy-paste between envs | Same config, different state files |
| Collaboration | One person at a time. Need to duplicate assistants, tools, etc. | Team can collaborate and use git branching |
| Reproducibility | "It worked on my assistant!" | Declarative, version-controlled |
| Disaster Recovery | Hope you have backups | Re-apply from git |
- Audit Trail β Every change is a commit with author, timestamp, and reason
- Code Review β Catch misconfigurations before they hit production
- Environment Parity β Dev, staging, and prod stay in sync
- No Drift β Git is the truth; manual console changes get overwritten
- Automation Ready β Plug into CI/CD pipelines
| Resource | Status | Format |
|---|---|---|
| Assistants | β | .md (with system prompt) or .yml |
| Tools | β | .yml |
| Structured Outputs | β | .yml |
| Squads | β | .yml |
| Personalities | β | .yml |
| Scenarios | β | .yml |
| Simulations | β | .yml |
| Simulation Suites | β | .yml |
- Node.js installed
- Vapi API token
npm install# Create your .env file with your Vapi token
echo "VAPI_TOKEN=your-token-here" > .env.dev| Command | Description |
|---|---|
npm run build |
Type-check the codebase |
npm run pull:dev |
Pull resources from Vapi to local files |
npm run pull:prod |
Pull resources from prod |
npm run apply:dev |
Push local files to Vapi (dev) |
npm run apply:prod |
Push local files to Vapi (prod) |
npm run call:dev -- -a <name> |
Start a WebSocket call to an assistant (dev) |
npm run call:dev -- -s <name> |
Start a WebSocket call to a squad (dev) |
# Pull existing resources from Vapi
npm run pull:dev
# Make changes to YAML/MD files...
# Push changes back to Vapi
npm run apply:devvapi-gitops/
βββ src/
β βββ apply.ts # Apply entry point & functions
β βββ pull.ts # Pull entry point & functions
β βββ call.ts # WebSocket call script
β βββ types.ts # TypeScript interfaces
β βββ config.ts # Environment & configuration
β βββ api.ts # Vapi HTTP client
β βββ state.ts # State file management
β βββ resources.ts # Resource loading (YAML, MD, TS)
β βββ resolver.ts # Reference resolution
β βββ delete.ts # Deletion & orphan checks
βββ resources/
β βββ assistants/ # Assistant files (.md or .yml)
β βββ tools/ # Tool YAML files
β βββ structuredOutputs/ # Structured output YAML files
β βββ squads/ # Squad YAML files
β βββ simulations/ # Simulation resources
β βββ personalities/ # Personality YAML files
β βββ scenarios/ # Scenario YAML files
β βββ tests/ # Simulation YAML files
β βββ suites/ # Simulation suite YAML files
βββ .env.dev # Dev environment secrets (gitignored)
βββ .env.prod # Prod environment secrets (gitignored)
βββ .vapi-state.dev.json # Dev state file
βββ .vapi-state.prod.json # Prod state file
Assistants with system prompts use Markdown with YAML frontmatter. The system prompt is written as readable Markdown below the config:
---
name: My Assistant
voice:
provider: 11labs
voiceId: abc123
model:
model: gpt-4o
provider: openai
toolIds:
- my-tool
firstMessage: Hello! How can I help you?
---
# Identity & Purpose
You are a helpful assistant for Acme Corp.
# Conversation Flow
1. Greet the user
2. Ask how you can help
3. Resolve their issue
# Rules
- Always be polite
- Never make up informationBenefits:
- System prompts are readable Markdown (not escaped YAML strings)
- Proper syntax highlighting in editors
- Easy to write headers, lists, tables
- Configuration stays cleanly separated at the top
Simple assistants without custom system prompts use plain YAML:
name: Simple Assistant
voice:
provider: vapi
voiceId: Elliot
model:
model: gpt-4o-mini
provider: openai
firstMessage: Hello!type: function
function:
name: get_weather
description: Get the current weather for a location
parameters:
type: object
properties:
location:
type: string
description: The city name
required:
- location
server:
url: https://my-api.com/weathername: Call Summary
type: ai
description: Summarizes the key points of a call
schema:
type: object
properties:
summary:
type: string
sentiment:
type: string
enum: [positive, neutral, negative]
assistant_ids:
- my-assistantname: Support Squad
members:
- assistantId: intake-agent
assistantDestinations:
- type: assistant
assistantId: specialist-agent
message: Transferring you to a specialist.
- assistantId: specialist-agentPersonality (simulations/personalities/):
name: Skeptical Sam
description: A doubtful caller who questions everything
prompt: You are skeptical and need convincing before trusting information.Scenario (simulations/scenarios/):
name: Happy Path - New Customer
description: New customer calling to schedule an appointment
prompt: |
You are a new customer calling to schedule your first appointment.
Be cooperative and provide all requested information.Simulation (simulations/tests/):
name: Booking Test Case 1
personalityId: skeptical-sam
scenarioId: happy-path-new-customerSimulation Suite (simulations/suites/):
name: Booking Flow Tests
simulationIds:
- booking-test-case-1
- booking-test-case-2
- booking-test-case-3Option 1: With System Prompt (recommended)
Create resources/assistants/my-assistant.md:
---
name: My Assistant
voice:
provider: 11labs
voiceId: abc123
model:
model: gpt-4o
provider: openai
toolIds:
- my-tool
---
# Your System Prompt Here
Instructions for the assistant...Option 2: Without System Prompt
Create resources/assistants/my-assistant.yml:
name: My Assistant
voice:
provider: vapi
voiceId: Elliot
model:
model: gpt-4o-mini
provider: openaiThen apply:
npm run apply:devCreate resources/tools/my-tool.yml:
type: function
function:
name: do_something
description: Does something useful
parameters:
type: object
properties:
input:
type: string
required:
- input
server:
url: https://my-api.com/endpointUse the filename without extension as the resource ID:
# In an assistant
model:
toolIds:
- my-tool # β resources/tools/my-tool.yml
- utils/helper-tool # β resources/tools/utils/helper-tool.yml
artifactPlan:
structuredOutputIds:
- call-summary # β resources/structuredOutputs/call-summary.yml# In a squad
members:
- assistantId: intake-agent # β resources/assistants/intake-agent.md# In a simulation
personalityId: skeptical-sam # β resources/simulations/personalities/skeptical-sam.yml
scenarioId: happy-path # β resources/simulations/scenarios/happy-path.yml- Remove references to the resource from other files
- Delete the file:
rm resources/tools/my-tool.yml - Apply:
npm run apply:dev
The engine will:
- Detect the resource is in state but not in filesystem
- Check for orphan references (will error if still referenced)
- Delete from Vapi
- Remove from state file
Create subdirectories for multi-tenant or feature organization:
resources/
βββ assistants/
β βββ shared/
β β βββ fallback.md
β βββ client-a/
β βββ support.md
βββ tools/
β βββ shared/
β β βββ transfer-call.yml
β βββ client-a/
β βββ custom-api.yml
Reference using full paths:
model:
toolIds:
- shared/transfer-call
- client-a/custom-apiPull (dependency order):
- Tools
- Structured Outputs
- Assistants
- Squads
- Personalities
- Scenarios
- Simulations
- Simulation Suites
Apply (dependency order):
- Tools β 2. Structured Outputs β 3. Assistants β 4. Squads
- Personalities β 6. Scenarios β 7. Simulations β 8. Simulation Suites
Delete (reverse dependency order):
- Simulation Suites β 2. Simulations β 3. Scenarios β 4. Personalities
- Squads β 6. Assistants β 7. Structured Outputs β 8. Tools
The engine automatically resolves resource IDs to Vapi UUIDs:
# You write:
toolIds:
- my-tool
# Engine sends to API:
toolIds:
- "uuid-1234-5678-abcd"Tracks mapping between resource IDs and Vapi UUIDs:
{
"tools": {
"my-tool": "uuid-1234"
},
"assistants": {
"my-assistant": "uuid-5678"
},
"squads": {
"my-squad": "uuid-abcd"
},
"personalities": {
"skeptical-sam": "uuid-efgh"
}
}| Variable | Required | Description |
|---|---|---|
VAPI_TOKEN |
β | API authentication token |
VAPI_BASE_URL |
β | API base URL (defaults to https://api.vapi.ai) |
Some fields are excluded when writing to files (server-managed):
id,orgId,createdAt,updatedAtanalyticsMetadata,isDeletedisServerUrlSecretSet,workflowIds
The referenced resource doesn't exist. Check:
- File exists in correct folder
- Filename matches exactly (case-sensitive)
- Using filename without extension
- For nested resources, use full path (
folder/resource)
- Find which resources reference it (shown in error)
- Remove the references
- Apply again
- Then delete the resource file
Check the state file has correct UUID:
- Open
.vapi-state.{env}.json - Find the resource entry
- If incorrect, delete entry and re-run apply
Some properties can't be updated after creation. Add them to UPDATE_EXCLUDED_KEYS in src/config.ts.