Skip to content

Calls

The Calls API manages the lifecycle of video call sessions — from initiation through signaling to termination and billing capture.

PENDING ──► WAITING ──► ACTIVE ──► ENDING ──► COMPLETED
│ │
└── CANCELLED (link expired/ │
guest didn't show) │
FAILED (billing error)
StateDescription
pendingCall link was opened; guest is entering payment details
waitingFirst hold placed; waiting for host to join
activeBoth peers connected, timer running
endingEither party clicked End; billing capture in progress
completedSession finished, billing settled
cancelledGuest left before connecting, or link expired
failedBilling error prevented completion

Begin a call session. This endpoint:

  1. Validates the call link (short_code)
  2. Places a Stripe PaymentIntent hold for Window 1
  3. Creates a call session record
  4. Issues a WebSocket signaling token

Authentication: Requires a guest token or user access token.

POST /v1/calls/initiate
Content-Type: application/json
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"short_code": "abc123",
"payment_method_id": "pm_1OqBuv2eZvKYlo2C8XXXXXXXXXXX",
"guest_name": "Alex"
}
FieldTypeRequiredDescription
short_codestringYesThe call link short code from the URL
payment_method_idstringYesStripe PaymentMethod ID from Stripe Elements
guest_namestringNoDisplay name for the guest
HTTP/1.1 201 Created
Content-Type: application/json
{
"call": {
"id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"status": "waiting",
"short_code": "abc123",
"host": {
"id": "01JN2X4K8M3F7QPZRVWT6YBHCE",
"display_name": "Jane Smith"
},
"rate_per_minute": "2.00",
"window_size_minutes": 10,
"created_at": "2026-03-15T14:00:00Z"
},
"signaling_token": "st_01JN2X6M0P5H9SRBTXY8ZDKEFG",
"turn_credentials": {
"urls": ["turn:turn.vidivo.app:3478", "turns:turn.vidivo.app:5349"],
"username": "1710090000:01JN2X6M0P5H9SRBTXY8ZDKEFG",
"credential": "hmac-sha1-credential-here"
}
}
CodeStatusDescription
link_not_found404Short code does not exist or link is expired
link_exhausted409Link has reached its maximum use count
payment_failed402Stripe hold could not be placed
host_unavailable503Host is not currently available

End an active or waiting call session. Either the host or guest may call this endpoint.

Authentication: Bearer token for the authenticated user, or guest token.

POST /v1/calls/01JN2X6M0P5H9SRBTXY8ZDKEFG/end
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Content-Type: application/json
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001
{
"reason": "user_ended"
}
FieldTypeRequiredDescription
reasonstringNouser_ended, host_ended, timeout, billing_error
HTTP/1.1 200 OK
Content-Type: application/json
{
"call": {
"id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"status": "completed",
"duration_seconds": 450,
"billed_minutes": 8,
"total_charged": "16.00",
"currency": "usd",
"ended_at": "2026-03-15T14:07:30Z"
}
}
CodeStatusDescription
not_found404Call session not found
forbidden403Caller is not a participant in this call
invalid_state409Call is not in an endable state

Retrieve the current status and metadata for a call session.

Authentication: Bearer token (host or guest of the session).

GET /v1/calls/01JN2X6M0P5H9SRBTXY8ZDKEFG/status
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
HTTP/1.1 200 OK
Content-Type: application/json
{
"call": {
"id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"status": "active",
"short_code": "abc123",
"host": {
"id": "01JN2X4K8M3F7QPZRVWT6YBHCE",
"display_name": "Jane Smith"
},
"guest_name": "Alex",
"rate_per_minute": "2.00",
"window_size_minutes": 10,
"current_window": 1,
"window_start_at": "2026-03-15T14:00:30Z",
"next_hold_at": "2026-03-15T14:09:30Z",
"elapsed_seconds": 270,
"estimated_charge": "9.00",
"created_at": "2026-03-15T14:00:00Z",
"started_at": "2026-03-15T14:00:30Z"
}
}

WebRTC signaling is handled over a WebSocket connection separate from the REST API.

Endpoint: wss://signal.vidivo.app/ws?token=<signaling_token>

Obtain the signaling_token from POST /calls/initiate. Tokens are valid for 5 minutes and must be used immediately after initiating a call.

All WebSocket messages use JSON:

{
"type": "offer | answer | ice_candidate | peer_joined | peer_left | error",
"payload": { ... }
}
Guest Signal Server Host
│ │ │
│── connect(token) ────────►│ │
│ │ │
│ │◄── connect(token) ─────│
│ │ │
│ │──── peer_joined ───────►│
│◄──── peer_joined ─────────│ │
│ │ │
│──── offer (SDP) ─────────►│ │
│ │──── offer (SDP) ───────►│
│ │ │
│ │◄── answer (SDP) ────────│
│◄─── answer (SDP) ─────────│ │
│ │ │
│── ice_candidate ─────────►│──── ice_candidate ─────►│
│◄─ ice_candidate ──────────│◄─── ice_candidate ──────│
│ │ │
│════════════════ P2P WebRTC established ════════════│
TypeDirectionDescription
offerGuest → HostWebRTC SDP offer
answerHost → GuestWebRTC SDP answer
ice_candidateBothICE candidate for NAT traversal
peer_joinedServer → BothOther participant connected
peer_leftServer → BothOther participant disconnected
errorServer → ClientSignaling error

Terminal window
# Initiate a call
curl -X POST https://api.vidivo.app/v1/calls/initiate \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: $(uuidgen)" \
-d '{
"short_code": "abc123",
"payment_method_id": "pm_1OqBuv2eZvKYlo2C8XXXXXXXXXXX",
"guest_name": "Alex"
}'
# Get call status
curl https://api.vidivo.app/v1/calls/01JN2X6M0P5H9SRBTXY8ZDKEFG/status \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."
# End a call
curl -X POST https://api.vidivo.app/v1/calls/01JN2X6M0P5H9SRBTXY8ZDKEFG/end \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: $(uuidgen)" \
-d '{"reason":"user_ended"}'