Developers / REST API

HelpVox REST API

The HelpVox API lets you register your product catalog and read AI-handled call data. The voice agent answers callers using exactly the data you push here, so keep it in sync with your inventory system (a cron every 15–60 minutes is typical).

Authentication

Create an API key in Dashboard → Settings and send it as a Bearer token with every request:

Authorization: Bearer hv_live_...

All endpoints are scoped to your vendor account. Errors use HTTP status codes and a JSON body: {"error": {"code": "...", "message": "..."}}

Rate limits

1 000 requests per minute per API key, per endpoint — a busy catalog sync never blocks your order polling. Every response carries the standard headers; on 429 wait Retry-After seconds and retry.

X-RateLimit-Limit: 1000       // requests allowed per minute
X-RateLimit-Remaining: 997    // left in the current window
X-RateLimit-Reset: 1765790460 // unix time the window resets

HTTP/2 429                    // when exceeded:
Retry-After: 23               // seconds until you may retry
{"error": {"code": "rate_limited", "message": "Rate limit of 1000 requests per minute exceeded. Retry after 23s."}}

Products

POST /api/v1/products

Creates products. Unlike PUT, an external_id that already exists is an error instead of a silent overwrite — so a repeated import cannot quietly replace a name or a price. Accepts a single product object or the same { products: [...] } envelope as PUT.

curl -X POST https://yourdomain.com/api/v1/products \
  -H "Authorization: Bearer hv_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "SKU-1001",
    "name": "Trail running shoes X1",
    "description": "Lightweight trail shoes with Vibram sole...",
    "category": "Shoes",
    "brand": "Acme",
    "active": true,
    "variants": [
      {"sku": "SKU-1001-42-BLK", "label": "Size 42, black",
       "attributes": {"size": "42", "color": "black"},
       "barcode": "3800123456789", "weight_kg": 0.85,
       "country_code": "BG", "price": 189.99, "currency": "BGN", "stock_quantity": 12}
    ]
  }'

Request fields

Identical to PUT (see below), either as one object or wrapped in products[].

Response 201

{
  "results": [
    {"external_id": "SKU-1001", "id": "9f4e5b2a-...", "action": "created"}
  ],
  "embedded": 1
}

Errors

409 already_exists      — one or more external_ids already exist
                         {"error": {"code": "already_exists",
                                    "external_ids": ["SKU-1001"]}}
422 duplicate_external_id — the same external_id twice in one request
422 validation_error      — see details

Nothing is written when the request is rejected — fix the payload and send it again.

PUT /api/v1/products

Batch upsert (create or update) up to 500 products, keyed by your own external_id (SKU). The variant is what actually sells: it carries the price, the stock, the barcode and the weight. A product is only offered to callers in the countries its variants list, and a product with no variants is always treated as inactive — it has no price, so the agent can neither quote nor order it.

curl -X PUT https://yourdomain.com/api/v1/products \
  -H "Authorization: Bearer hv_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      {
        "external_id": "SKU-1001",
        "name": "Trail running shoes X1",
        "description": "Lightweight trail shoes with Vibram sole...",
        "category": "Shoes",
        "brand": "Acme",
        "active": true,
        "variants": [
          {"sku": "SKU-1001-42-BLK", "label": "Size 42, black",
           "attributes": {"size": "42", "color": "black"},
           "barcode": "3800123456789", "weight_kg": 0.85,
           "country_code": "BG", "price": 189.99, "currency": "BGN", "stock_quantity": 12},
          {"sku": "SKU-1001-43-BLK", "label": "Size 43, black",
           "attributes": {"size": "43", "color": "black"},
           "country_code": "BG", "price": 189.99, "currency": "BGN", "stock_quantity": 3},
          {"sku": "SKU-1001-42-BLK", "label": "Size 42, black",
           "country_code": "RO", "price": 389.99, "currency": "RON", "stock_quantity": 4}
        ]
      }
    ]
  }'

Request fields

products[]              required, 1–500 items
  external_id           string, required, 1–128 chars — your model code; the
                        upsert key that groups the variants. NOT a warehouse
                        SKU — that lives on each variant below.
  name                  string, required, 1–512 chars
  description           string | null, optional, ≤10000 chars
  category              string | null, optional, ≤256 chars
  brand                 string | null, optional, ≤256 chars
  attributes            object, optional — free-form key/value on the product itself
  active                boolean, optional, default true
  variants[]            required, 1–200 — no variants means nothing to sell
    sku                 string, optional, ≤128 chars — the SKU the warehouse
                        actually ships; empty falls back to external_id, which
                        is what a product with a single variant usually wants
    label               string, optional, ≤200 chars — what the agent says out
                        loud ("Size 42, black"); empty is built from attributes
    attributes          object, optional — what differs: {"size": "42"}
    barcode             string | null, optional, ≤64 chars — EAN/GTIN per variant
    weight_kg           number | null, optional — for courier labels and quotes
    country_code        string, required, ISO 3166-1 alpha-2 ("BG")
    price               number, required, ≥0
    currency            string, required, ISO 4217 ("EUR")
    stock_quantity      integer | null, optional — null = unlimited
    available           boolean, optional, default true

The same variant sold in two countries is two entries with the same sku and different country_code, price and currency. The list replaces every existing variant of the product, so a size you stop selling disappears by simply leaving it out.

The older offers[] field still works and becomes one unnamed variant per country, but it cannot express sizes or colours — prefer variants[].

Response 200

{
  "results": [
    {"external_id": "SKU-1001", "id": "9f4e5b2a-...", "action": "created"},
    {"external_id": "SKU-1002", "id": "b2c81d77-...", "action": "updated"}
  ],
  "embedded": 2
}

Validation failures return 422 with {"error": {"code": "validation_error", "details": {...}}}.

GET /api/v1/products?limit=100&offset=0

Lists your products with their variants. active comes back false for a product without variants, whatever you set. Max limit 500. All list endpoints return a meta object — page with offset until has_more is false.

Response 200

{
  "total": 128,
  "meta": {"total": 128, "limit": 100, "offset": 0, "has_more": true},
  "products": [
    {
      "id": "9f4e5b2a-...",
      "external_id": "SKU-1001",
      "name": "Trail running shoes X1",
      "description": "Lightweight trail shoes...",
      "category": "Shoes",
      "brand": "Acme",
      "attributes": {},
      "active": true,
      "updated_at": "2026-08-13T10:15:00.000Z",
      "variants": [
        {"id": "c71a0f38-...", "sku": "SKU-1001-42-BLK", "label": "Size 42, black",
         "attributes": {"size": "42", "color": "black"},
         "barcode": "3800123456789", "weight_kg": 0.85,
         "country_code": "BG", "price": 189.99, "currency": "BGN",
         "stock_quantity": 12, "available": true}
      ]
    }
  ]
}
GET /api/v1/products/{external_id}

Fetch a single product by your SKU.

DELETE /api/v1/products/{external_id}

Removes a product (and all its variants) from the catalog. The agent stops offering it immediately.

Orders & fulfilment integration (WMS / ERP)

The AI agent takes orders over the phone and website chat. Your warehouse, fulfilment or ERP system integrates in three steps: pull new orders, push status changes and tracking numbers back, and stream courier checkpoints — so the agent always tells customers where their parcel really is.

Order lifecycle

new (received) → processing (being picked) → prepared (packed, waiting for the courier) → in_transit (courier has it) → delivered. A parcel the courier could not hand over becomes delivery_failed and can go back to in_transit on a second attempt, or to returned. cancelled covers an order stopped before dispatch; returned covers a parcel that came back — refused at the door or sent back after delivery.

The agent sets new (order taken), cancelled (customer cancels before shipping) and return_requested. Your system owns processing, shipped, delivered and returned. Every order carries a short order_number the customer knows — all endpoints below accept it in place of the UUID.

POST /api/v1/orders

Push an order from your shop / WMS / ERP into HelpVox, so the AI agent answers questions about it (status, tracking, contents) exactly like for orders it took itself. Idempotent on external_ref — repeat POSTs return the existing order. Items are free-form snapshots; when sku matches a catalog product they get linked.

curl -X POST https://www.helpvox.ai/api/v1/orders \
  -H "Authorization: Bearer hv_live_..." -H "Content-Type: application/json" \
  -d '{
    "external_ref": "1393-629",
    "customer_name": "Florica Mitrasca",
    "customer_phone": "40755780257",
    "customer_email": "customer@example.com",
    "delivery_address": "str Principală 136, Cig",
    "delivery_city": "Cig",
    "delivery_street": "str Principală",
    "delivery_street_num": "136",
    "delivery_other": "entrance A, floor 5, first door left",
    "delivery_post_code": "317010",
    "address_meta": {"office_id": 1053, "city_id": 19901},
    "country_code": "RO",
    "currency": "RON",
    "courier": "econt",
    "courier_service": "econt_door",
    "payment_method": "cash_on_delivery",
    "payment_status": "pending",
    "status": "new",
    "shipping_amount": 19.99,
    "discount_amount": 5.00,
    "vat_rate": 19,
    "vat_amount": 17.26,
    "cod_amount": 104.99,
    "total_weight_kg": 1.45,
    "placed_at": "2026-08-18T09:14:00Z",
    "scheduled_at": "2026-08-21T14:00:00Z",
    "items": [
      {"sku": "TB-RO-001", "name": "Комплект TeenBook Romania", "quantity": 1,
       "unit_price": 90.00, "discount_amount": 5.00,
       "vat_rate": 19, "vat_amount": 13.57, "total_amount": 85.00}
    ]
  }'

Request fields

external_ref       string, optional, 1–120 chars — YOUR order id; idempotency key
customer_name      string, required, 2–200 chars
customer_phone     string, required, 5–32 chars — used by the agent to verify the caller
customer_email     string, optional, valid email
delivery_address   string, required, 5–400 chars — the address as one line

  The same address, split. Optional, but it is what lets HelpVox recognise a
  returning customer's address across conversations, and what a WMS needs to
  create a shipment without re-parsing free text.
delivery_city        string, optional — city name only
delivery_quarter     string, optional — quarter / neighbourhood name only
delivery_street      string, optional — street, number, block, floor, apartment
delivery_street_num  string, optional — the number on its own; "14A" and "16" on
                     the same street are different addresses to us and to the courier
delivery_building    string, optional
delivery_entrance    string, optional
delivery_floor       string, optional
delivery_apartment   string, optional
delivery_other       string, optional — landmark for the courier ("first door left")
delivery_office      string, optional — courier office, when delivering to one
delivery_office_code string, optional — the courier's own id for that office
delivery_post_code   string, optional — most couriers require it for door delivery
address_meta         object, optional — the courier's own nomenclature ids
                     (office_id, city_id, street_id…). Stored and returned
                     untouched; we never interpret them.

country_code       string, optional, ISO 3166-1 alpha-2, default "BG"
currency           string, required, ISO 4217 — applies to all item prices
payment_method     string, optional, default "cash_on_delivery"
payment_status     "pending" | "collected" | "paid_out", optional
status             "new" | "processing" | "prepared" | "in_transit" |
                   "delivery_failed" | "delivered" | "cancelled" | "returned"
                   optional, default "new"
tracking_number    string, optional, ≤80 chars
courier            string, optional, ≤60 chars — the courier company
courier_service    string, optional, ≤60 chars — door or office, e.g.
                   "econt_office", "speedy_door"; it changes the price and the label
notes              string, optional, ≤1000 chars
channel            "phone" | "chat" | "web" | "import", optional
placed_at          ISO 8601 datetime, optional — when the order was really placed.
                   Send it for orders imported after the fact, otherwise an order
                   from three days ago looks like it arrived just now.
scheduled_at       ISO 8601 datetime, optional — the delivery slot the customer asked for

  Money. Send what you already calculated — nothing here is recomputed.
shipping_amount    number, optional — delivery fee
discount_amount    number, optional — discount on the whole order
vat_rate           number, optional, 0–100
vat_amount         number, optional
total_amount       number, optional — the order total
cod_amount         number, optional — what the courier collects on delivery;
                   lower than total_amount when part was already paid
total_weight_kg    number, optional — for the courier quote and label
items[]            required, 1–100
  sku              string, optional — matched against your catalog VARIANT SKUs,
                   so send the variant's SKU (size/colour), not the parent's
  name             string, required, 1–512 chars — snapshot, survives catalog edits
  quantity         integer, required, 1–500
  unit_price       number, required, ≥0 — in "currency"
  discount_amount  number, optional — on this line, in money (convert percentages)
  vat_rate         number, optional, 0–100 — per line, because one order can mix
                   rates (in Bulgaria books are 9% and toys 20%) and such an order
                   cannot be invoiced from a single order-level rate
  vat_amount       number, optional
  total_amount     number, optional — defaults to quantity × unit_price − discount

subtotal_amount is always Σ quantity × unit_price. When you omit total_amount it defaults to subtotal + shipping − discount; send your own value whenever you round or price differently, and it is stored as given — we never overwrite it, because you know how you calculated it and we do not.

Response 201 (created)

{
  "created": true,
  "id": "42134ce2-...",
  "order_number": "754489",
  "external_ref": "1393-629",
  "total": "90.00 RON"
}

Response 200 (already exists — same external_ref)

{
  "created": false,
  "id": "42134ce2-...",
  "order_number": "754489",
  "external_ref": "1393-629"
}

Customers can then ask the agent about the order using either number (yours or ours) + the phone the order was made with.

GET /api/v1/orders?status=new&limit=50

Poll for orders to fulfil (recommended: every 1–5 minutes). Filter by any status, e.g. ?status=return_requested for returns to process.

Response 200

{
  "meta": {"total": 3, "limit": 50, "offset": 0, "has_more": false},
  "orders": [
    {
      "id": "42134ce2-...",
      "order_number": "754489",
      "external_ref": "1393-629",        // null for agent-taken orders
      "source": "api",                   // "agent", "api", or the connected
                                         // system's name, e.g. "bigarena"
      "status": "new",
      "payment_status": "pending",       // null | "pending" | "collected" | "paid_out"
      "channel": "chat",                 // "phone" | "chat"
      "customer_name": "Florica Mitrasca",
      "customer_phone": "40755780257",
      "customer_email": "customer@example.com",
      "delivery_address": "str Principală 136, Cig",
      "delivery_city": "Cig",
      "delivery_quarter": null,
      "delivery_street": "str Principală",
      "delivery_street_num": "136",
      "delivery_building": null,
      "delivery_entrance": "A",
      "delivery_floor": "5",
      "delivery_apartment": "3",
      "delivery_other": "first door left",
      "delivery_office": null,
      "delivery_office_code": null,
      "delivery_post_code": "317010",
      "address_meta": {"office_id": 1053, "city_id": 19901},
      "country_code": "RO",
      "payment_method": "cash_on_delivery",
      "tracking_number": null,
      "courier": "econt",
      "courier_service": "econt_door",
      "subtotal_amount": "90.00",     // always Σ quantity × unit_price
      "shipping_amount": "19.99",
      "discount_amount": "5.00",
      "vat_rate": "19.00",
      "vat_amount": "17.26",
      "total_amount": "104.99",
      "cod_amount": "104.99",         // what the courier collects on delivery
      "total_weight_kg": "1.450",
      "currency": "RON",
      "placed_at": "2026-08-18T09:14:00.000Z",
      "scheduled_at": "2026-08-21T14:00:00.000Z",
      "notes": null,
      "cancel_reason": null,
      "return_reason": null,
      "created_at": "2026-08-13T16:02:11.000Z",
      "updated_at": "2026-08-13T16:02:11.000Z",
      "items": [
        {
          "product_id": "9f4e5b2a-...",  // null when no catalog match
          "variant_id": "c71a0f38-...",  // the matched variant, null when none
          "sku": "TB-RO-001",            // your SKU (snapshot)
          "name": "Комплект TeenBook Romania",
          "variant": "Size 42, black",   // null when the product has one variant
          "quantity": 1,
          "unit_price": "90.00",
          "discount_amount": "5.00",
          "vat_rate": "19.00",
          "vat_amount": "13.57",
          "total_amount": "85.00",
          "currency": "RON"
        }
      ]
    }
  ]
}
PATCH /api/v1/orders/{id_or_ref}

Push fulfilment progress back. The path accepts our short order_number, the order UUID, or your external_ref.

Request fields (any subset, ≥1)

status            "processing" | "prepared" | "in_transit" | "delivery_failed" | "delivered" | "cancelled" | "returned"
                  (the old names "packed", "shipped" and "return_requested" are still accepted
                   and stored as "prepared", "in_transit" and "returned")
tracking_number   string, ≤80 chars
courier           string, ≤60 chars — the courier company
courier_service   string, ≤60 chars — door or office, e.g. "econt_office"
payment_status    "pending" | "collected" | "paid_out"   optional, stored but not shown
                  in the dashboard — HelpVox does not track COD settlement itself
curl -X PATCH https://www.helpvox.ai/api/v1/orders/1393-629 \
  -H "Authorization: Bearer hv_live_..." \
  -H "Content-Type: application/json" \
  -d '{"status": "in_transit", "tracking_number": "SP123456789BG", "courier": "Speedy", "payment_status": "collected"}'

Response 200

{"ok": true, "order_number": "754489", "status": "in_transit"}

Response 409 (invalid status transition)

{
  "error": {
    "code": "invalid_transition",
    "message": "Cannot move order from 'cancelled' to 'shipped'. Allowed: none."
  }
}

Once a tracking number is set, the agent reads it to customers who ask about their order.

POST /api/v1/orders/{order_number}/events

Stream courier checkpoints (single event or {"events": [...]} up to 100). Re-sending the same events is safe — duplicates are ignored — so full re-syncs are fine. The agent answers "where is my parcel right now?" with the latest checkpoint.

curl -X POST https://www.helpvox.ai/api/v1/orders/19849/events \
  -H "Authorization: Bearer hv_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"occurred_at": "2026-08-12T08:30:00Z", "description": "Handed to courier", "location": "Sofia warehouse", "source": "speedy"},
      {"occurred_at": "2026-08-12T14:05:00Z", "description": "Ready for pickup at office Kyuchuk Parizh", "location": "Plovdiv", "source": "speedy"}
    ]
  }'

Event fields

occurred_at   string, required, ISO 8601 datetime ("2026-08-12T08:30:00Z")
description   string, required, 2–400 chars — spoken to customers nearly verbatim
location      string, optional, ≤200 chars
source        string, optional, ≤60 chars (e.g. "speedy", "packeta")

Response 200

{"ok": true, "received": 2, "inserted": 2}

inserted < received means duplicates were skipped (same order + timestamp + description). Write descriptions in the language your customers speak.

GET /api/v1/orders/{id_or_ref}/events

Full checkpoint history for an order, oldest first.

Response 200

{
  "order_number": "754489",
  "events": [
    {
      "occurred_at": "2026-08-12T08:30:00.000Z",
      "description": "Handed to courier",
      "location": "Sofia warehouse",
      "source": "speedy"
    }
  ]
}
GET /api/v1/orders/{id_or_ref}

Fetch a single order — by our short number, the UUID or your external_ref.

Response 200

{
  "id": "42134ce2-...",
  "order_number": "754489",
  "external_ref": "1393-629",
  "source": "api",
  "status": "in_transit",
  "payment_status": "collected",
  "tracking_number": "SP123456789BG",
  "courier": "Speedy",
  "courier_service": "speedy_door",
  "customer_name": "Florica Mitrasca",
  "customer_phone": "40755780257",
  "delivery_address": "str Principală 136, Cig",
  "delivery_city": "Cig",
  "delivery_quarter": null,
  "delivery_street": "str Principală",
  "delivery_street_num": "136",
  "delivery_building": null,
  "delivery_entrance": "A",
  "delivery_floor": "5",
  "delivery_apartment": "3",
  "delivery_other": "first door left",
  "delivery_office": null,
  "delivery_office_code": null,
  "delivery_post_code": "317010",
  "address_meta": {"office_id": 1053, "city_id": 19901},
  "subtotal_amount": "90.00",
  "shipping_amount": "19.99",
  "discount_amount": "5.00",
  "vat_rate": "19.00",
  "vat_amount": "17.26",
  "total_amount": "104.99",
  "cod_amount": "104.99",
  "total_weight_kg": "1.450",
  "placed_at": "2026-08-18T09:14:00.000Z",
  "scheduled_at": "2026-08-21T14:00:00.000Z",
  "currency": "RON",
  "created_at": "2026-08-13T16:02:11.000Z",
  "items": [
    {
      "product_id": "9f4e5b2a-...",
      "variant_id": "c71a0f38-...",
      "sku": "TB-RO-001",
      "name": "Комплект TeenBook Romania",
      "variant": "Size 42, black",
      "quantity": 1,
      "unit_price": "90.00",
      "discount_amount": "5.00",
      "vat_rate": "19.00",
      "vat_amount": "13.57",
      "total_amount": "85.00"
    }
  ]
}

Integration tips

  • Keep stock in sync via PUT /api/v1/products — the agent refuses to sell items you mark unavailable.
  • Push shipped + tracking as one PATCH the moment the label is printed.
  • Statuses you push are reflected to customers instantly — there is no cache to wait for.
  • All requests are idempotent or safely repeatable; retry on any 5xx with backoff.

Calls

GET /api/v1/calls?limit=50&offset=0

Lists handled calls and chats, newest first, each with the AI analysis summary.

Response 200

{
  "meta": {"total": 42, "limit": 50, "offset": 0, "has_more": false},
  "calls": [
    {
      "id": "6f0f8f9e-...",
      "channel": "phone",              // "phone" | "chat"
      "caller_number": "+359•••123",   // masked; null for web chats
      "language": "bg",
      "status": "completed",           // "completed" | "failed"
      "duration_seconds": 184,
      "started_at": "2026-08-13T09:12:44.000Z",
      "analysis": {
        "purpose": "Клиентът пита за наличност",
        "topic": "Наличност на продукт",
        "sentiment": "positive",       // "positive" | "neutral" | "negative"
        "follow_up_needed": false
      }
    }
  ]
}
GET /api/v1/calls/{id}

Full call detail: metadata, the complete transcript and the full analysis.

{
  "id": "6f0f8f9e-...",
  "caller_number": "+3598•••123",
  "language": "bg",
  "duration_seconds": 184,
  "transcript": [
    {"role": "agent", "message": "Здравейте! ...", "timeInCallSecs": 0},
    {"role": "user", "message": "Здравейте, искам да питам за...", "timeInCallSecs": 4}
  ],
  "analysis": {
    "purpose": "Клиентът пита за наличност на обувки",
    "topic": "Наличност на продукт",
    "summary": "...",
    "resolution": "...",
    "sentiment": "positive",
    "follow_up_needed": false,
    "products_mentioned": ["Trail running shoes X1"]
  }
}

Knowledge base (FAQ)

PUT /api/v1/knowledge

Replaces your entire FAQ with the provided items (full-sync, like the catalog). The agent searches these during calls for policy/company questions. Up to 500 items.

{
  "items": [
    {"question": "Работите ли с наложен платеж?", "answer": "Да, чрез Еконт и Спиди."},
    {"question": "Имате ли физически магазин?", "answer": "Да, в София, бул. Витоша 100."}
  ]
}
GET /api/v1/knowledge

Lists your current FAQ entries.

Response 200

{
  "items": [
    {"id": "c1d2e3f4-...", "question": "Работите ли с наложен платеж?", "answer": "Да, чрез Еконт и Спиди."}
  ]
}

Callback requests

GET /api/v1/callbacks?status=pending&limit=100&offset=0

Lists callback requests the AI agent registered when a caller needed a human (complaints, order issues, questions outside the catalog). Integrate this with your CRM or ticketing system, or handle them from the dashboard. Filter with status=pending|done.

Response 200

{
  "meta": {"total": 2, "limit": 100, "offset": 0, "has_more": false},
  "callbacks": [
    {
      "id": "a7b8c9d0-...",
      "caller_name": "Георги Иванов",       // null if not given
      "phone_number": "0889123456",
      "reason": "Проблем с доставка на поръчка",
      "country_code": "BG",
      "status": "pending",                   // "pending" | "done"
      "created_at": "2026-08-13T11:14:40.000Z"
    }
  ]
}
PATCH /api/v1/callbacks/{id}

Mark a callback as handled from your CRM/ticketing system.

curl -X PATCH https://www.helpvox.ai/api/v1/callbacks/a7b8c9d0-... \
  -H "Authorization: Bearer hv_live_..." -H "Content-Type: application/json" \
  -d '{"status": "done", "outcome": "спокоен, проблемът е решен"}'

Response 200

{"ok": true, "id": "a7b8c9d0-...", "status": "done"}

Appointments

GET /api/v1/appointments?status=requested&limit=50&offset=0

Booking/appointment requests the AI agent took (service businesses). Statuses: requested → confirmed → done, or cancelled.

Response 200

{
  "meta": {"total": 1, "limit": 50, "offset": 0, "has_more": false},
  "appointments": [
    {
      "id": "b3c4d5e6-...",
      "status": "requested",
      "channel": "phone",
      "customer_name": "Мария Иванова",
      "customer_phone": "0888123456",
      "service": "Сватбен грим",
      "preferred_time": "петък след 14 ч.",
      "confirmed_for": null,             // ISO datetime once confirmed
      "notes": null,
      "outcome": null,
      "created_at": "2026-08-14T09:12:00.000Z"
    }
  ]
}
PATCH /api/v1/appointments/{id}

Move an appointment from your CRM or calendar system. confirmed_for (ISO 8601) is required when confirming.

curl -X PATCH https://www.helpvox.ai/api/v1/appointments/b3c4d5e6-... \
  -H "Authorization: Bearer hv_live_..." -H "Content-Type: application/json" \
  -d '{"status": "confirmed", "confirmed_for": "2026-08-16T10:00:00+03:00"}'

Response 200

{"ok": true, "id": "b3c4d5e6-...", "status": "confirmed"}

Account

GET /api/v1/me

Returns your vendor profile, plan and voice-minute usage, phone numbers, chat agents and catalog size. Useful as a health-check for your integration.

Response 200

{
  "vendor": {
    "id": "5253ddef-...",
    "name": "Издателство Пайви",
    "slug": "paivy",
    "status": "active",
    "plan": "growth",                  // null = няма избран план
    "plan_status": "active",
    "elevenlabs_key_configured": false
  },
  "usage": {"minutes_used": 42, "minutes_included": 3600},
  "numbers": [
    {"id": "…", "country_code": "BG", "phone_number": "+1862…", "language": "bg", "status": "active", "agent_provisioned": true}
  ],
  "chat_agents": [
    {"id": "…", "language": "bg", "agent_provisioned": true}
  ],
  "product_count": 6
}

Prefer an AI assistant instead of code?

Everything above is also available as a remote MCP server, so Claude, Codex or Cursor can work with your orders and catalogue directly — no API key, just a sign-in. Connect an AI assistant →

Going live — checklist

  1. Push your product catalog via PUT /api/v1/products (or add products in the dashboard).
  2. Fill in your company profile and FAQ so the agent can answer general questions.
  3. Buy a phone number in Twilio for each target country.
  4. Register the number in Dashboard → Phone numbers and provision the AI agent.
  5. Attach the Twilio number (SID + auth token) — calls are now answered 24/7.
  6. Watch transcripts and analyses arrive in Dashboard → Calls.