API Documentation

Integrate VoiceServant into your systems to transcribe audio and extract structured data.

Sign in to generate API keys and configure webhooks.Sign in

How the VoiceServant API works

VoiceServant uses a template-first approach to audio processing and extraction:

  • Templates define the structure of extracted data
  • Templates are created once in VoiceServant Studio
  • The API processes audio files against a template
  • All processing is asynchronous
  • Results are delivered via webhooks
Think of templates as a contract between your audio files and the structured output you receive. Define the schema once, then process any number of audio files against it.

Templates

Templates are the foundation of VoiceServant's extraction model. Every API request references a template ID.

What templates define

  • The fields to extract from audio
  • Data types and formats for each field
  • How fields map to spreadsheet columns
  • Validation rules and transformations

Creating templates

Templates are created and managed in VoiceServant Studio. Upload a sample audio file, define your columns, and the template is ready to use via the API.

Templates are reusable. Create once, process unlimited audio files. Templates cannot be created via the API — they require the Studio interface.

Quick Start

The typical workflow for integrating VoiceServant:

1

Create a template in VoiceServant Studio

Upload a sample audio file, define columns, and configure extraction settings.

2

Generate an API key

Go to the Developers section in your account to create and manage API keys.

3

Send audio files to the API with a template ID

POST audio files to /run referencing your template.

4

Receive structured results via webhook

Configure a webhook endpoint to receive job completion notifications — runs, templates, users, and more.


Authentication

All API requests must include your API key in the Authorization header using the Bearer scheme.

Authorization: Bearer sk_<key_id>_<secret>

API keys follow the format sk_<uuid>_<random>. The raw key is returned once at creation and never stored — save it securely.

Keep your API keys secret. Never expose them in client-side code, public repositories, or share them with unauthorized users. If a key is compromised, revoke it immediately from the Developers page.

API Keys

API keys let external services authenticate without user sessions. They are scoped, revocable, and optionally expiring. Only team owners can create and manage keys.

Scopes

Each key carries a set of scopes that limit what it can access. Grant only what the integration needs.

ScopePermission
read:teamRead team details
read:runRead audio runs
read:templateRead templates
read:userRead user data
read:statisticsRead usage statistics
write:teamModify team settings
write:runCreate/modify audio runs
write:templateCreate/modify templates
write:userCreate/modify users

Key Management Endpoints

API keys are created and managed through your VoiceServant Platform account — not via an API key itself.


Endpoints

All endpoints are relative to the base API URL:

https://api.voiceservant.com

Runs

Submit a Run

POST/v1/run

Submit a document for extraction using a specified template.

Request Fields

FieldTypeDescription
template_id*stringThe ID of the template to use for extraction
files*filesThe audio or video files (MP3, WAV, M4A, MP4, etc.)
output_modestringControls how results are structured: "combined" merges all documents into a single list; "single" keeps each document's data separate. Defaults to the template's output_mode.
store_databooleanWhether to retain uploaded files after the run completes. Set to false on privacy-sensitive workloads to purge source files automatically. Defaults to the template's store_data setting.

Example Request

curl -X POST https://api.voiceservant.com/v1/run \
  -H 'Authorization: Bearer <sk_your_live_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "template_id": "<your-template-uuid>",
    "output_mode": "combined",
    "store_data": true,
    "files": [
      {
        "id": "abc123",
        "filename": "meeting.mp3",
        "content_type": "audio/mpeg",
        "file_size": 102400
      }
    ]
  }'

Example Response

{
  "success": true,
  "message": "Successfully generated upload URLs for 1 files",
  "data": {
    "uploads": [
      {
        "document_id": "<document-uuid>",
        "status": "pending",
        "filename": "meeting.mp3",
        "expires_in": 3600,
        "upload_url": "https://voiceservant-platform-documents.s3.amazonaws.com/",
        "upload_fields": {
          "Content-Type": "audio/mpeg",
          "key": "<s3-upload-key>",
          "x-amz-algorithm": "AWS4-HMAC-SHA256",
          "x-amz-credential": "<aws-credential>",
          "x-amz-date": "<aws-date>",
          "x-amz-security-token": "<aws-security-token>",
          "policy": "<base64-encoded-policy>",
          "x-amz-signature": "<aws-signature>"
        },
        "key": "<s3-upload-key>"
      }
    ],
    "run_id": "<run-uuid>"
  }
}

Get Run Status

GET/v1/run/:run_id

Check the status of a run and retrieve outputs when complete.

Example Request

curl https://api.voiceservant.com/v1/run/<your-run-uuid> \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json"

Example Response

{
  "success": true,
  "message": "Get Run",
  "data": {
    "run": {
      "run_id": "<run-uuid>",
      "template_id": "<template-uuid>",
      "template_name": "Meeting Processing",
      "team_id": "<team-uuid>",
      "status": "completed",
      "audio_duration": 23.064,
      "avg_time_to_process": 11360,
      "completed_at": "2026-04-22T09:53:21.536042+00:00",
      "created_at": "2026-04-22T09:52:39.540081+00:00",
      "created_by": "<user-uuid>",
      "updated_at": "2026-04-22T09:53:22.602502+00:00",
      "deleted_at": null,
      "extracted_data_bucket_key": "<s3-extraction-key>",
      "extracted_data_excel_bucket_key": null,
      "documents": [
        {
          "document_id": "<document-uuid>",
          "name": "meeting.mp3",
          "status": "completed",
          "audio_duration": 23.064,
          "time_to_process": 11360.758,
          "bucket_key": "<s3-bucket-key>",
          "extracted_data_bucket_key": "<s3-extraction-key>",
          "created_at": "2026-04-22T09:52:38.581051+00:00",
          "updated_at": "2026-04-22T09:53:21.407282+00:00",
          "extracted_data": {
            "note": "This object structure depends on your template's defined fields",
            "ExampleGroup": {
              "Field One": "value",
              "Field Two": "value"
            }
          },
          "preview_url": "<presigned-s3-preview-url>"
        }
      ],
      "extracted_data": [
        {
          "note": "This array structure depends on your template's defined fields",
          "ExampleGroup": {
            "Field One": "value",
            "Field Two": "value"
          }
        }
      ]
    }
  }
}

For failed runs, the response includes an error field with details:

{
  "run_id": "<run-uuid>",
  "status": "failed",
  "error": {
    "code": "EXTRACTION_FAILED",
    "message": "Unable to process audio file. The file may be corrupted or in an unsupported format."
  }
}

output_mode

Controls how document extraction results are structured once all documents complete. combined (default) merges all document extractions into a single list accessible as extracted_data at the run level. single keeps each document's own extracted_data without a run-level merge. When omitted from the create request, the run inherits the value from the template.

store_data

Controls whether the original uploaded files are kept in storage after the run completes. When true (default), files are retained and each document includes a signed preview_url. When false, files are deleted from storage once the run reaches a terminal state and preview_url is not returned. Use store_data: false on privacy-sensitive workloads where source documents should be purged automatically after extraction. When omitted, the run inherits the value from the template.


Team

Get Team Details

GET/v1/team

Retrieve details about the authenticated user's team, including plan information and quota usage.

Example Request

curl https://api.voiceservant.com/v1/team \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json"

Example Response

{
  "success": true,
  "message": "Successfully get team",
  "data": {
    "team_id": "<team-uuid>",
    "name": "Your Team Name",
    "api_access_enabled": true,
    "preferred_model": "gpt-5.4-mini",
    "preferred_transcript_model": "gpt-4o-transcribe",
    "is_custom_plan": 0,
    "webhook_mask": null,
    "stripe_customer_id": "<stripe-customer-id>",
    "stripe_subscription_id": "<stripe-subscription-id>",
    "stripe_schedule_id": "<stripe-schedule-id>",
    "enforce_add_new_payment_method": false,
    "created_at": "2026-03-13T13:38:55.387267+00:00",
    "updated_at": "2026-04-28T10:41:07.096285+00:00",
    "deleted_at": null,
    "plan": {
      "key": "voiceservant_platform_standard",
      "name": "VoiceServant Platform - Standard",
      "type": "STANDARD",
      "audio_duration_quota": 15000,
      "current_audio_duration_usage": 840.86,
      "last_reported_seconds_usage": 0,
      "overage_allowed": true,
      "overage_cost_per_minute": 0.08,
      "overage_unit_size": 60,
      "overage_cost_per_unit": 0.08,
      "price_monthly": 25,
      "recurring": true,
      "seats": 1,
      "has_unpaid_invoices": false,
      "start_date": "2026-04-06T09:07:56.549326+00:00",
      "expiration_date": "2026-05-06T09:07:56.549326+00:00",
      "created_at": "2026-04-06T09:07:56.549326+00:00",
      "updated_at": "2026-04-06T09:07:56.549326+00:00"
    }
  }
}

List Team Members

GET/v1/user

Retrieve a paginated list of all members in the authenticated user's team.

Example Request

curl https://api.voiceservant.com/v1/user \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json"

Example Response

{
  "success": true,
  "message": "Successfully get list of users",
  "data": {
    "items": [
      {
        "user_id": "<user-uuid>",
        "team_id": "<team-uuid>",
        "email": "[email protected]",
        "first_name": "Jane",
        "last_name": "Doe",
        "role": "owner",
        "status": "active",
        "email_verified": true,
        "last_login": "2026-04-29T14:31:05.154539+00:00",
        "created_at": "2026-03-13T13:38:56.749083+00:00",
        "updated_at": "2026-04-29T14:31:04.435740+00:00",
        "deleted_at": null
      },
      {
        "user_id": "<user-uuid>",
        "team_id": "<team-uuid>",
        "email": "[email protected]",
        "first_name": "John",
        "last_name": "Smith",
        "role": "admin",
        "status": "pending",
        "email_verified": false,
        "last_login": "2026-03-18T08:18:05.714621+00:00",
        "created_at": "2026-03-18T08:16:56.736168+00:00",
        "updated_at": "2026-03-18T08:18:05.714639+00:00",
        "deleted_at": null
      }
    ],
    "pagination": {
      "all_pages": 1,
      "current_page": 1,
      "total_items": 2
    }
  }
}

Template

List Templates

GET/v1/template

Retrieve a paginated list of all extraction templates for your team.

Example Request

curl https://api.voiceservant.com/v1/template \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json"

Example Response

{
  "success": true,
  "message": "Successfully get list of jobs",
  "data": {
    "items": [
      {
        "template_id": "<template-uuid>",
        "team_id": "<team-uuid>",
        "name": "Meeting Processing",
        "output_mode": "single",
        "prompt_instructions": "Extract all relevant meeting data",
        "store_data": false,
        "total_runs": 4,
        "last_run": "2026-04-28T10:41:08.320130+00:00",
        "created_at": "2026-04-28T10:22:49.194772+00:00",
        "created_by": "<user-uuid>",
        "updated_at": "2026-04-28T13:44:48.346157+00:00",
        "updated_by": "<user-uuid>",
        "deleted_at": null,
        "sheets": [
          {
            "sheet_id": "<sheet-uuid>",
            "name": "Meeting",
            "prompt_instructions": "",
            "included_in_extraction": true,
            "single_object_extraction": false,
            "parent_sheet_id": null,
            "parent_sheet_name": null,
            "parent_sheet_column_id": null,
            "parent_sheet_column_name": null,
            "created_at": "2026-04-28T10:22:49.193908+00:00",
            "created_by": "<user-uuid>",
            "updated_at": "2026-04-28T10:22:49.193908+00:00",
            "updated_by": "<user-uuid>",
            "columns": [
              {
                "column_id": "<column-uuid>",
                "name": "Topic",
                "data_type": "string",
                "enabled": true,
                "prompt_instructions": "",
                "field_prompts": null,
                "query": null,
                "enabled_reference_mapping": false,
                "reference_data_set_id": null,
                "return_data_set_field_id": null,
                "created_at": "2026-04-28T10:22:49.193908+00:00",
                "created_by": "<user-uuid>",
                "updated_at": "2026-04-28T10:22:49.193908+00:00",
                "updated_by": "<user-uuid>"
              },
              {
                "column_id": "<column-uuid>",
                "name": "Matched Category",
                "data_type": "lookup",
                "enabled": true,
                "prompt_instructions": "",
                "field_prompts": {},
                "query": null,
                "enabled_reference_mapping": true,
                "reference_data_set_id": "<dataset-uuid>",
                "return_data_set_field_id": "<field-uuid>",
                "created_at": "2026-04-28T10:22:49.193908+00:00",
                "created_by": "<user-uuid>",
                "updated_at": "2026-04-28T10:22:49.193908+00:00",
                "updated_by": "<user-uuid>"
              }
            ],
            "children": []
          }
        ]
      }
    ],
    "pagination": {
      "current_page": 1,
      "all_pages": 1,
      "total_items": 1
    }
  }
}

Create a Template

POST/v1/template

Create a new extraction template with sheets and columns defining the structure of data to extract.

Request Fields

FieldTypeDescription
name*stringDisplay name for the template
prompt_instructionsstringGlobal extraction instructions applied to all sheets
output_mode*stringExtraction output mode. Use "single" for most cases
store_data*booleanWhether to persist extracted data on the server
sheets*arrayArray of sheet objects defining the extraction schema (see below)

Example Request

curl -X POST https://api.voiceservant.com/v1/template \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Meeting Processing Template",
    "prompt_instructions": "Extract all relevant information from the meeting recording",
    "output_mode": "single",
    "store_data": true,
    "sheets": [
      {
        "name": "Meeting Summary",
        "prompt_instructions": "Extract the main meeting summary",
        "included_in_extraction": true,
        "single_object_extraction": true,
        "columns": [
          { "name": "Topic",       "enabled": true, "prompt_instructions": "Extract the main meeting topic" },
          { "name": "Date",        "enabled": true, "prompt_instructions": "Extract the meeting date in YYYY-MM-DD format" },
          { "name": "Duration",    "enabled": true, "prompt_instructions": "Extract the meeting duration in minutes" }
        ],
        "children": []
      },
      {
        "name": "Action Items",
        "prompt_instructions": "Extract all action items discussed",
        "included_in_extraction": true,
        "single_object_extraction": false,
        "columns": [
          { "name": "Task",        "enabled": true, "prompt_instructions": "Extract the task description" },
          { "name": "Assignee",    "enabled": true, "prompt_instructions": "Extract the person responsible" },
          { "name": "Due Date",    "enabled": true, "prompt_instructions": "Extract the due date if mentioned" }
        ],
        "children": []
      }
    ]
  }'

Example Response

{
  "success": true,
  "message": "Created Template",
  "data": {
    "template": {
      "template_id": "<template-uuid>",
      "team_id": "<team-uuid>",
      "name": "Meeting Processing Template",
      "output_mode": "single",
      "prompt_instructions": "Extract all relevant information from the meeting recording",
      "store_data": true,
      "total_runs": 0,
      "last_run": null,
      "created_at": "2026-04-29T15:41:45.442903+00:00",
      "created_by": "<user-uuid>",
      "updated_at": "2026-04-29T15:41:45.442903+00:00",
      "updated_by": "<user-uuid>",
      "deleted_at": null,
      "sheets": [
        {
          "sheet_id": "<sheet-uuid>",
          "name": "Meeting Summary",
          "prompt_instructions": "Extract the main meeting summary",
          "included_in_extraction": true,
          "single_object_extraction": true,
          "parent_sheet_id": null,
          "parent_sheet_name": null,
          "parent_sheet_column_id": null,
          "parent_sheet_column_name": null,
          "created_at": "2026-04-29T15:41:45.441983+00:00",
          "created_by": "<user-uuid>",
          "updated_at": "2026-04-29T15:41:45.441983+00:00",
          "updated_by": "<user-uuid>",
          "columns": [
            {
              "column_id": "<column-uuid>",
              "name": "Topic",
              "data_type": "string",
              "enabled": true,
              "prompt_instructions": "Extract the main meeting topic",
              "field_prompts": null,
              "query": null,
              "enabled_reference_mapping": false,
              "reference_data_set_id": null,
              "return_data_set_field_id": null,
              "created_at": "2026-04-29T15:41:45.441983+00:00",
              "created_by": "<user-uuid>",
              "updated_at": "2026-04-29T15:41:45.441983+00:00",
              "updated_by": "<user-uuid>"
            }
          ],
          "children": []
        }
      ]
    }
  }
}

Statistics

GET/v1/statistics

Retrieve usage statistics for your team including token consumption, run counts, and overage details.

Example Request

curl https://api.voiceservant.com/v1/statistics \
  -H "Authorization: Bearer <sk_your_live_api_key>" \
  -H "Content-Type: application/json"

Example Response

{
  "success": true,
  "message": "Successfully fetched dashboard statistics",
  "data": {
    "templates": 10,
    "extractions": 39,
    "current_audio_duration_usage": 840.86,
    "avg_processing_time_in_seconds": 21.0,
    "overage": {
      "allowed": true,
      "seconds_left": 14159.14,
      "overage_seconds": 0,
      "cost_per_minute": 0.08,
      "overage_unit_size": 60,
      "overage_cost": 0.0,
      "overage_cost_per_unit": 0.08
    }
  }
}

Webhooks

Webhooks push real-time event notifications to your server whenever something changes — a run is created, a template is updated, a user is removed, etc.

How webhooks work

  1. A team owner registers a webhook with an HTTPS endpoint URL and an event type.
  2. When the event occurs, the platform publishes it to AWS EventBridge.
  3. An EventBridge rule triggers the delivery function, which POSTs the payload to your endpoint.
  4. The request is signed with HMAC-SHA256 so you can verify it came from VoiceServant.

Event Types

EventTriggered when
create.userA new user is added to the team
update.userA user record is modified
delete.userA user is removed
create.templateA new template is created
update.templateA template is modified
delete.templateA template is deleted
create.runAn audio run is started
update.runA run is updated
delete.runA run is deleted

Payload Format

Every delivery is an HTTP POST with Content-Type: application/json.

{
  "event": "create.run",
  "team_id": "your-team-uuid",
  "data": {
    // full object data for the affected resource
  }
}

Request Headers

HeaderDescription
Content-Typeapplication/json
X-Webhook-Signaturesha256=<hex_signature>
X-Webhook-TimestampUnix timestamp (seconds) of the delivery

Verifying the Signature

Always verify the signature before processing a webhook. Compute HMAC-SHA256 over {timestamp}.{raw_body} using your team's webhook secret, where timestamp is the value from X-Webhook-Timestamp.

// Node.js
const crypto = require("crypto");

function verifyWebhook(secret, timestamp, body, signatureHeader) {
  const message = `${timestamp}.${body}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(message, "utf8")
    .digest("hex");
  const received = signatureHeader.replace("sha256=", "");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
# Python
import hmac, hashlib

def verify_webhook(secret, timestamp, body, signature_header):
    message = f"{timestamp}.{body}"
    expected = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()
    received = signature_header.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)
Use a timing-safe comparison (timingSafeEqual / compare_digest) to prevent timing attacks.

Delivery Behavior

  • Method: HTTP POST, timeout: 30 seconds
  • No automatic retries — failed deliveries are logged but not re-sent
  • Each webhook records the last delivery attempt in last_delivery
Make your handler idempotent and consider polling the API for missed events if uptime guarantees are critical.

Security Notes

  • HTTPS endpoints only — plaintext HTTP is not accepted
  • Validate X-Webhook-Signature on every delivery before trusting the payload
  • Rotate the secret if it is ever exposed
  • To temporarily stop deliveries, set is_active: false rather than deleting

Error Handling

Runs may fail due to unclear audio, invalid templates, or processing errors. Failed runs return a webhook event with error details. Clients should handle retries where appropriate.

HTTP Status Codes

CodeMeaning
200Success
400Bad request — Check your request body or parameters
401Unauthorized — Invalid or missing API key
403Forbidden — Key inactive, revoked, expired, or missing required scope
404Not found — Key, run, or template doesn't exist
429Rate limited — Too many requests, slow down
500Server error — Something went wrong on our end

Examples

Process a Meeting into JSON

Submit a meeting recording for audio processing and fetch the structured JSON output:

# Submit the recording
curl -X POST https://api.voiceservant.com/v1/jobs \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -F "templateId=tpl_meetings" \
  -F "[email protected]"

# Response: { "jobId": "job_abc123", "status": "queued" }

# Poll for completion (or use webhooks)
curl https://api.voiceservant.com/v1/jobs/job_abc123 \
  -H "Authorization: Bearer sk_live_your_api_key"

# When status is "completed", fetch the JSON output
curl "https://storage.voiceservant.com/outputs/job_abc123.json?token=..."

Bulk Upload and Append to Workbook

When you submit multiple audio files with the same template, each job produces its own output files. To consolidate results, fetch individual JSON outputs and merge them in your application, or download each XLSX and combine sheets programmatically.

# Submit multiple audio files
for file in recordings/*.mp3; do
  curl -X POST https://api.voiceservant.com/v1/jobs \
    -H "Authorization: Bearer sk_live_your_api_key" \
    -F "templateId=tpl_meetings" \
    -F "file=@$file"
done

Handle Webhook Callback (Node.js)

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/voiceservant', (req, res) => {
  const signature = req.headers['x-voiceservant-signature'];
  const timestamp = req.headers['x-voiceservant-timestamp'];
  const rawBody = req.body.toString('utf-8');

  // Verify signature
 const message = `${timestamp}.${rawBody}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(message, 'utf8')
    .digest('hex');
  const received = signature.replace('sha256=', '');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    return res.status(401).send('Invalid signature');
  }

  const { event, team_id, data } = JSON.parse(rawBody);

  if (event === 'create.run') {
    console.log(`New run started for team ${team_id}`, data);
  }

  res.status(200).send('OK');
});