Stop Choosing Between Answering Calls and Living Your Life

Developer Docs

API Reference

Comprehensive API documentation for integrating with the Optiphone platform. Base URL: https://app.optiphone.ai

Authentication

Session

Most endpoints require an HTTP-only session cookie. Login via POST /api/login to receive one.

API Key

Server-to-server endpoints accept the x-optiphone-api-key header or an apiKey query parameter. Obtain your API key from Settings → API Tokens in the Optiphone dashboard.

Public

Registration, login, password reset, and invitation endpoints are publicly accessible — no auth required.

Authentication

POST/api/register

Register a new user account

Request Body

Content-Type: application/json

Schema

{
  "email": "string (required, valid email)",
  "username": "string (required, min 3 characters)",
  "password": "string (required, min 8 characters)",
  "firstName": "string (optional)",
  "lastName": "string (optional)"
}

Example

{
  "email": "[email protected]",
  "username": "johndoe",
  "password": "securepassword123",
  "firstName": "John",
  "lastName": "Doe"
}

Response Example

{
  "id": 1,
  "email": "[email protected]",
  "username": "johndoe",
  "firstName": "John",
  "lastName": "Doe",
  "systemRole": "user"
}
POST/api/login

Authenticate user and create session

Request Body

Content-Type: application/json

Schema

{
  "username": "string (required, case-insensitive)",
  "password": "string (required)"
}

Example

{
  "username": "johndoe",
  "password": "securepassword123"
}

Response Example

{
  "id": 1,
  "email": "[email protected]",
  "username": "johndoe",
  "firstName": "John",
  "lastName": "Doe"
}
Note: Sets HTTP-only session cookie. Username can be email or username (case-insensitive).
POST/api/logout

Logout and destroy session

Response Example

{
  "success": true
}
GET/api/user-profile

Get current authenticated user profile with organization memberships

Response Example

{
  "id": 1,
  "email": "[email protected]",
  "username": "johndoe",
  "firstName": "John",
  "lastName": "Doe",
  "systemRole": "user",
  "organizationMemberships": []
}
PUT/api/user-profile

Update current user profile

Request Body

Content-Type: application/json

Schema

{
  "firstName": "string (optional)",
  "lastName": "string (optional)",
  "email": "string (optional, valid email)",
  "lastActiveOrganizationId": "number (optional)"
}

Example

{
  "firstName": "Jane",
  "lastName": "Smith",
  "email": "[email protected]"
}
GET/api/role-management/me

Get current user with full organization details

POST/api/password-reset/request

Request password reset email

Request Body

Content-Type: application/json

Schema

{
  "email": "string (required, valid email)"
}
POST/api/password-reset/validate

Validate password reset token

Request Body

Content-Type: application/json

Schema

{
  "token": "string (required)"
}
POST/api/password-reset/confirm

Confirm password reset with new password

Request Body

Content-Type: application/json

Schema

{
  "token": "string (required)",
  "newPassword": "string (required, min 8 characters)"
}

Organizations

GET/api/organization

Get current organization settings

Response Example

{
  "id": 1,
  "organizationId": 1,
  "serviceArea": "4218, 4220",
  "defaultJobStatus": "New",
  "callForwardingNumber": "+61400000000"
}
PUT/api/organization

Update current organization settings (requires org_admin)

Request Body

Content-Type: application/json

Schema

{
  "serviceArea": "string (optional)",
  "callPreviewWaitTime": "number (optional)",
  "defaultJobStatus": "string (optional)",
  "callForwardingNumber": "string (optional)",
  "operationhours": "string (optional)",
  "responsetime": "string (optional)"
}
GET/api/organization/subscription

Get current organization subscription

GET/api/organization/features

Get current organization features and access status

GET/api/organization/usage

Get current organization usage statistics

Assistants

GET/api/assistants

Get all assistants (filtered by user's organizations)

Query Parameters

organizationIdnumber— Filter by organization ID

Response Example

[
  {
    "assistantId": "assistant-123",
    "name": "Customer Service Assistant",
    "phoneNumber": "+61400000000",
    "organizationId": 1
  }
]
GET/api/assistants/:assistantId

Get single assistant by ID

Path Parameters

assistantIdstring— Assistant ID
POST/api/assistants

Create new assistant

Request Body

Content-Type: application/json

Schema

{
  "assistantId": "string (optional, auto-generated if not provided)",
  "name": "string (optional)",
  "phoneNumber": "string (optional)",
  "description": "string (optional)",
  "organizationId": "number (optional, uses current org if not provided)",
  "isActive": "boolean (optional)",
  "greetingPhrase": "string (optional)",
  "businessHours": "string (optional, JSON)",
  "prompt": "string (optional)"
}
PUT/api/assistants/:assistantId

Update assistant (requires org_admin for assistant's organization)

Path Parameters

assistantIdstring— Assistant ID

Request Body

Content-Type: application/json

Schema

{
  "name": "string (optional)",
  "phoneNumber": "string (optional)",
  "description": "string (optional)",
  "organizationId": "number (optional)",
  "isActive": "boolean (optional)",
  "greetingPhrase": "string (optional)",
  "businessHours": "string (optional)",
  "prompt": "string (optional)"
}
Note: Updating prompt creates a new version automatically
DELETE/api/assistants/:assistantId

Delete assistant (requires org_admin)

Path Parameters

assistantIdstring— Assistant ID
GET/api/assistants/:assistantId/prompt-versions

Get prompt version history for assistant

Path Parameters

assistantIdstring— Assistant ID
POST/api/assistants/:assistantId/prompt-versions/:versionId/activate

Activate a specific prompt version

Path Parameters

assistantIdstring— Assistant ID
versionIdnumber— Version ID

Call Logs

GET/api/call-logs

Get call logs with filtering and pagination

Query Parameters

startDatestring (ISO date)— Start date filter
endDatestring (ISO date)— End date filter
searchstring— Search in transcript/summary
assistantIdstring— Filter by assistant
organizationIdnumber— Filter by organization
postcodestring— Filter by caller postcode
callerNamestring— Filter by caller name
phoneNumberstring— Filter by phone number
successRatingnumber— Filter by success rating (1-10)
callReasonstring— Filter by call reason
statusstring— Filter by call status
durationnumber— Filter by minimum duration (seconds)
pagenumber— Page number (default: 1)
limitnumber— Items per page (default: 50)

Response Example

{
  "calls": [],
  "total": 0,
  "page": 1,
  "limit": 50
}
GET/api/call-logs/:callId

Get single call log by ID

Path Parameters

callIdstring— Call log ID
Note: Response includes bookedJob: { id, startAt, calendarName } | null — the calendar appointment booked during this call by the voice agent (links the call to the job), or null if no booking was made.
GET/api/call-logs/export

Export call logs as CSV

Query Parameters

startDatestring (ISO date)— Start date filter
endDatestring (ISO date)— End date filter
searchstring— Search filter
assistantIdstring— Filter by assistant
organizationIdnumber— Filter by organization
Note: Returns CSV file download
PUT/api/call-logsAPI Key

Create a call log from an external provider (ElevenLabs, etc.)

Query Parameters

apiKeystringrequired— Your Optiphone API key (alternatively pass as x-optiphone-api-key header)

Request Body

Content-Type: application/json

Schema

{
  "id": "string (optional, auto-generated if omitted)",
  "externalId": "string (optional, provider-side call ID for idempotency)",
  "assistantId": "string (optional)",
  "organizationId": "number (optional)",
  "isAssigned": "boolean (optional)",
  "source": "string (optional, e.g. \"elevenlabs\" | \"vapi\")",
  "callReason": "string (optional)",
  "status": "string (optional)",
  "endedReason": "string (optional)",
  "successEvaluation": "number 1–10 (optional)",
  "startedAt": "string | number | Date (optional, ISO 8601 or Unix ms)",
  "endedAt": "string | number | Date (optional)",
  "phoneNumber": "string (optional)",
  "customerNumber": "string (optional)",
  "callerName": "string (optional)",
  "callerEmail": "string (optional)",
  "callerAddress": "string (optional)",
  "callerPostcode": "string (optional)",
  "transcript": "string (optional)",
  "summary": "string (optional)",
  "recordingUrl": "string (optional)",
  "messages": "array (optional)",
  "analysis": "object (optional)",
  "structuredOutputs": "object (optional)",
  "cost": "number (optional)",
  "costBreakdown": "object (optional)",
  "durationSeconds": "number (optional)"
}

Example

{
  "externalId": "el_call_abc123",
  "source": "elevenlabs",
  "assistantId": "asst_xyz",
  "organizationId": 1,
  "status": "completed",
  "endedReason": "customer-ended-call",
  "successEvaluation": 8,
  "startedAt": "2025-02-24T10:00:00.000Z",
  "endedAt": "2025-02-24T10:03:45.000Z",
  "durationSeconds": 225,
  "phoneNumber": "+61299990000",
  "customerNumber": "+61400000001",
  "callerName": "Jane Smith",
  "callerEmail": "[email protected]",
  "callerPostcode": "2000",
  "transcript": "Agent: Hello, how can I help you today?...",
  "summary": "Customer enquired about service availability.",
  "recordingUrl": "https://storage.elevenlabs.io/recordings/el_call_abc123.mp3",
  "cost": 0.045,
  "costBreakdown": {
    "tts": 0.02,
    "stt": 0.025
  }
}

Response Example

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "el_call_abc123",
  "assistantId": "asst_xyz",
  "organizationId": 1,
  "source": "elevenlabs",
  "status": "completed",
  "endedReason": "customer-ended-call",
  "successEvaluation": 8,
  "createdAt": 1740391200000,
  "updatedAt": 1740391200000,
  "startedAt": 1740391200000,
  "endedAt": 1740391425000,
  "phoneNumber": "+61299990000",
  "customerNumber": "+61400000001",
  "callerName": "Jane Smith",
  "callerEmail": "[email protected]",
  "callerPostcode": "2000",
  "transcript": "Agent: Hello, how can I help you today?...",
  "summary": "Customer enquired about service availability.",
  "recordingUrl": "https://storage.elevenlabs.io/recordings/el_call_abc123.mp3",
  "durationSeconds": 225
}
Note: Requires x-optiphone-api-key header or apiKey query parameter. If externalId is provided alongside source, duplicate deliveries are rejected with 409 Conflict.
PUT/api/call-logs/:callIdAPI Key

Upsert a call log by provider-assigned ID — creates if not found, updates if it exists

Path Parameters

callIdstring— Provider call ID used as the record's primary key

Query Parameters

apiKeystringrequired— Your Optiphone API key (alternatively pass as x-optiphone-api-key header)

Request Body

Content-Type: application/json

Schema

{
  "externalId": "string (optional, provider-side call ID for idempotency)",
  "assistantId": "string (optional)",
  "organizationId": "number (optional)",
  "source": "string (optional, e.g. \"elevenlabs\" | \"vapi\")",
  "callReason": "string (optional)",
  "status": "string (optional)",
  "endedReason": "string (optional)",
  "successEvaluation": "number 1–10 (optional)",
  "startedAt": "string | number | Date (optional)",
  "endedAt": "string | number | Date (optional)",
  "phoneNumber": "string (optional)",
  "customerNumber": "string (optional)",
  "callerName": "string (optional)",
  "callerEmail": "string (optional)",
  "callerAddress": "string (optional)",
  "callerPostcode": "string (optional)",
  "transcript": "string (optional)",
  "summary": "string (optional)",
  "recordingUrl": "string (optional)",
  "messages": "array (optional)",
  "analysis": "object (optional)",
  "structuredOutputs": "object (optional)",
  "cost": "number (optional)",
  "costBreakdown": "object (optional)",
  "durationSeconds": "number (optional)"
}

Example

{
  "externalId": "el_call_abc123",
  "source": "elevenlabs",
  "assistantId": "asst_xyz",
  "organizationId": 1,
  "status": "completed",
  "endedReason": "customer-ended-call",
  "successEvaluation": 8,
  "startedAt": "2025-02-24T10:00:00.000Z",
  "endedAt": "2025-02-24T10:03:45.000Z",
  "durationSeconds": 225,
  "phoneNumber": "+61299990000",
  "customerNumber": "+61400000001",
  "callerName": "Jane Smith",
  "callerEmail": "[email protected]",
  "callerPostcode": "2000",
  "transcript": "Agent: Hello, how can I help you today?...",
  "summary": "Customer enquired about service availability.",
  "recordingUrl": "https://storage.elevenlabs.io/recordings/el_call_abc123.mp3",
  "cost": 0.045,
  "costBreakdown": {
    "tts": 0.02,
    "stt": 0.025
  }
}
Note: Requires x-optiphone-api-key header or apiKey query parameter. Idempotent — safe to retry.

Contacts

GET/api/contacts

Get all contacts for organization

Query Parameters

organizationIdnumberrequired— Organization ID
searchstring— Search in name, email, phone
limitnumber— Limit results
offsetnumber— Offset for pagination
GET/api/contacts/:id

Get single contact by ID

Path Parameters

idnumber— Contact ID

Query Parameters

organizationIdnumberrequired— Organization ID
POST/api/contacts

Create new contact

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "firstName": "string (optional)",
  "lastName": "string (optional)",
  "email": "string (optional)",
  "phone": "string (optional)",
  "businessName": "string (optional)",
  "address": "string (optional)",
  "postcode": "string (optional)",
  "tags": "array<string> (optional)"
}
PUT/api/contacts/:id

Update contact

Path Parameters

idnumber— Contact ID

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "firstName": "string (optional)",
  "lastName": "string (optional)",
  "email": "string (optional)",
  "phone": "string (optional)",
  "businessName": "string (optional)",
  "address": "string (optional)",
  "postcode": "string (optional)",
  "tags": "array<string> (optional)",
  "lastInteractionAt": "number (optional, timestamp)",
  "lastInteractionType": "string (optional)",
  "lastInquiryStatus": "string (optional)"
}
DELETE/api/contacts/:id

Delete contact (soft delete)

Path Parameters

idnumber— Contact ID

Query Parameters

organizationIdnumberrequired— Organization ID

Metrics

GET/api/metrics/:assistantId

Get assistant metrics (total calls, avg duration, success rate)

Path Parameters

assistantIdstring— Assistant ID

Query Parameters

timeRangestring— Time range (e.g., 'January 2024' or ISO date range)

Response Example

{
  "totalCalls": 150,
  "avgDuration": 120,
  "successRate": 85.5
}
GET/api/call-volume/:assistantId

Get call volume data by date

Path Parameters

assistantIdstring— Assistant ID

Query Parameters

timeRangestring— Time range

Response Example

[
  {
    "date": "2024-01-01",
    "count": 5
  },
  {
    "date": "2024-01-02",
    "count": 8
  }
]

Billing

GET/api/billing/balance

Get current organization credit balance and plan info

Response Example

{
  "balance": {
    "balance": 100,
    "totalEarned": 500,
    "totalSpent": 400
  },
  "subscription": {
    "planId": "solo",
    "status": "active"
  },
  "planPricing": {
    "monthlyPrice": null,
    "includedMinutes": null
  },
  "monthlyUsage": 50
}
GET/api/billing/plans

Get available subscription plans with pricing

Response Example

{
  "plans": [
    {
      "planId": "solo",
      "name": "Solo",
      "monthlyPrice": null,
      "includedMinutes": null
    }
  ]
}
GET/api/billing/history

Get credit transaction history

Query Parameters

typestring— Transaction type (subscription, refill, usage, adjustment)
startDatenumber (timestamp)— Start date
endDatenumber (timestamp)— End date
limitnumber— Limit results (default: 100)
offsetnumber— Offset (default: 0)
POST/api/billing/checkout/plan

Create Stripe checkout session for plan upgrade

Request Body

Content-Type: application/json

Schema

{
  "planId": "string (required, one of: tradesman, crew, enterprise)"
}

Response Example

{
  "checkoutUrl": "https://checkout.stripe.com/..."
}
POST/api/billing/checkout/refill

Create Stripe checkout session for credit refill

Request Body

Content-Type: application/json

Schema

{
  "minutes": "number (required)",
  "planId": "string (required)"
}
POST/api/billing/portal

Create Stripe billing portal session

Response Example

{
  "portalUrl": "https://billing.stripe.com/..."
}
POST/api/billing/payment-method

Update payment method

Request Body

Content-Type: application/json

Schema

{
  "paymentMethodId": "string (required, Stripe payment method ID)"
}
GET/api/billing/payment-method

Get current payment method

Webhooks

POST/api/subscriptions/webhookAPI Key

Webhook endpoint for Stripe and internal Optiphone webhooks

Query Parameters

apiKeystringrequired— Your Optiphone API key (alternatively pass as x-optiphone-api-key header)
Note: Requires either Stripe signature (stripe-signature header) or Optiphone API key (x-optiphone-api-key header).

Notifications

GET/api/notification-preferences

Get user notification preferences

PUT/api/notification-preferences

Update user notification preferences

Request Body

Content-Type: application/json

Schema

{
  "pushNotificationsEnabled": "boolean (optional)",
  "quietHoursEnabled": "boolean (optional)",
  "quietHoursStart": "string (optional, HH:mm format)",
  "quietHoursEnd": "string (optional, HH:mm format)",
  "notifyOnNewCall": "boolean (optional)"
}
POST/api/push-notifications/subscribe

Subscribe to push notifications

Request Body

Content-Type: application/json

Schema

{
  "subscription": {
    "endpoint": "string (required, URL)",
    "keys": {
      "p256dh": "string (required)",
      "auth": "string (required)"
    }
  }
}
POST/api/push-notifications/unsubscribe

Unsubscribe from push notifications

GET/api/push-notifications/vapid-key

Get VAPID public key for push notifications

Preferences

GET/api/user-preferences

Get user preferences

PUT/api/user-preferences

Update user preferences

Request Body

Content-Type: application/json

Schema

{
  "preferences": "object (required, JSON object)"
}

Invitations

GET/api/invitations/validate

Validate organization invitation token

Query Parameters

tokenstringrequired— Invitation token
POST/api/invitations/accept

Accept organization invitation

Request Body

Content-Type: application/json

Schema

{
  "token": "string (required)",
  "password": "string (required, min 8 characters, if creating new account)",
  "email": "string (optional, if creating new account)",
  "username": "string (optional, if creating new account)"
}

Agent Tools (Voice Agent Webhooks)

POST/api/agent-tools/check-availabilityAPI Key

Voice-agent webhook: find open appointment slots on the org's calendar during a live call.

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required) — the calling org (supplied by the agent, not the caller)",
  "call_sid": "string (required) — Twilio call SID; cross-checked against the active call",
  "with_person": "string (optional) — a named team member to book with; omit for next available",
  "days": "number (optional) — lookahead window in days (default 7)"
}

Example

{
  "organizationId": 1,
  "call_sid": "CA…",
  "with_person": "Dylan",
  "days": 7
}

Response Example

{
  "hasAvailability": true,
  "slots": [
    "Wednesday 1 Jul, 9:00 AM",
    "Wednesday 1 Jul, 10:00 AM"
  ]
}
Note: Callable by the Optiphone voice agent (internal secret) or by an external automation with a per-org API token (Authorization: Bearer <token>, scope read:jobs). The token can only act on its own org.
POST/api/agent-tools/book-appointmentAPI Key

Voice-agent webhook: book a chosen slot into the org's calendar during a live call.

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "start_at": "number (required) — chosen slot start, epoch ms, from check-availability",
  "call_sid": "string (required)",
  "contact_phone": "string (required) — caller's number for the booking",
  "contact_name": "string (optional)",
  "reason": "string (optional) — what the appointment is for",
  "with_person": "string (optional) — named team member to book with"
}

Example

{
  "organizationId": 1,
  "start_at": 1782730800000,
  "call_sid": "CA…",
  "contact_phone": "+61…",
  "contact_name": "Jane Smith",
  "reason": "Quote for switchboard upgrade"
}

Response Example

{
  "confirmed": true,
  "jobId": 482,
  "startAt": 1782730800000,
  "endAt": 1782734400000
}
Note: Voice-agent or per-org-API-token tool (see the Agent Tools section note for token scopes).On conflict/failure returns { confirmed: false, spoken: "…" } with a voice-friendly message instead of booking.
POST/api/agent-tools/find-appointmentsAPI Key

Voice-agent webhook: list a caller's upcoming appointments (earliest first) so the agent can answer "when is my appointment?" and pick one to change — without inventing details.

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "call_sid": "string (required)",
  "phone": "string (optional) — number the appointments are under; defaults to the caller's number"
}

Example

{
  "organizationId": 1,
  "call_sid": "CA…",
  "phone": "+61…"
}

Response Example

{
  "appointments": [
    {
      "appointmentId": 482,
      "when": "Wednesday 1 Jul, 9:00 AM",
      "startAt": 1782730800000,
      "calendarName": "Dylan Amey",
      "withPerson": "Dylan",
      "reason": "Quote"
    }
  ]
}
Note: Voice-agent or per-org-API-token tool (see the Agent Tools section note for token scopes).Returns only upcoming, non-cancelled appointments, ordered earliest first (the first item is the next appointment).
POST/api/agent-tools/reschedule-appointmentAPI Key

Voice-agent webhook: move a specific existing appointment to a new slot during a live call (no duplicate left behind).

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "appointmentId": "number (required) — from find-appointments",
  "newStartAt": "number (required) — new slot start, epoch ms, from check-availability",
  "call_sid": "string (required)"
}

Example

{
  "organizationId": 1,
  "appointmentId": 482,
  "newStartAt": 1782817200000,
  "call_sid": "CA…"
}

Response Example

{
  "rescheduled": true,
  "jobId": 482,
  "startAt": 1782817200000,
  "endAt": 1782820800000,
  "when": "Thursday 2 Jul, 9:00 AM",
  "calendarName": "Dylan Amey"
}
Note: Voice-agent or per-org-API-token tool (see the Agent Tools section note for token scopes).Re-validates the new slot is free; the old slot is released. Returns { rescheduled: false, spoken } (404) when the appointment isn't found.
POST/api/agent-tools/cancel-appointmentAPI Key

Voice-agent webhook: cancel a specific existing appointment during a live call.

Request Body

Content-Type: application/json

Schema

{
  "organizationId": "number (required)",
  "appointmentId": "number (required) — from find-appointments",
  "call_sid": "string (required)"
}

Example

{
  "organizationId": 1,
  "appointmentId": 482,
  "call_sid": "CA…"
}

Response Example

{
  "cancelled": true,
  "jobId": 482
}
Note: Voice-agent or per-org-API-token tool (see the Agent Tools section note for token scopes).Soft-cancels (status → Cancelled); the slot reopens. Returns { cancelled: false, spoken } (404) when the appointment isn't found.