WiseParts
Calls

List request history

Every Voice API request is recorded with its payload, response, status and duration.
GET
https://app.wiseparts.ai/api/v1/voice

Every Voice API request is recorded with its payload, response, status and duration. This returns that history for your account.

Send at least one filter[...] key or the request fails

A request that carries no filter parameter at all answers 500. Before anything is validated, the endpoint merges its own account scoping into the filter you sent, and when you sent none there is nothing to merge into — the merge itself raises the error, so the response is a bare "Server Error" with no hint of the cause. This is how the endpoint behaves today rather than a rule it means to enforce; code around it, do not read intent into it.

Any one bracketed key clears it. The samples below send filter[scopeBetween], which is why it is marked required — a date range is usually what you wanted anyway. If you really want the unfiltered history, filter[scopeOnlyErrors]=0 is the no-op that costs nothing: a falsy value applies no condition but still gives the merge something to work with.

There is no default ordering. Pass sort when order matters, or the entries come back in whatever order the analytics store finds them — which is not reliably newest first.

A request that fails authentication is never recorded, because the account could not be identified. Check the token first when nothing appears at all. Requests to this endpoint are not recorded either, so the history only ever contains /voice/call and /voice/priority.

The response envelope depends on limit

Omit limit and the body is a bare JSON array of entries — the whole history, unpaginated and uncapped, which on a busy account is a great deal of data. Send limit and the body becomes an object with data, links and meta. Send limit together with without_pagination=1 and it is a bare array again, capped at limit. Decide which shape you want before writing the client, because they are not interchangeable.

Filters are unvalidated, not forgiving

The two filters below are sent bracketed, as filter[...], because they are named scopes rather than plain columns.

Only an unrecognised scope-prefixed key is dropped in silence — it is reported internally and ignored, and the result comes back unfiltered. Every other unrecognised key is used, not dropped: it becomes a partial LIKE on a column of that name. So filter[endpoint]=voice/call quietly works even though it is not documented here, while a typo such as filter[endpiont]=voice/call queries a column that does not exist and returns 500. Misspell a scope and you get too many rows; misspell anything else and you get an error.

sort is the one parameter that checks what it was given, refusing a field it cannot sort by with a 400.

Query parameters

filter[scopeBetween]stringRequired

Restrict to a date range, written as from,to. Both bounds are whole days and inclusive: the first counts from the start of that day, the second to the end of it. Omit the second and it defaults to the end of today, so 2026-08-01, reads as "since the first". A value that is not a parseable date, or an empty one, returns 500. Marked required because the endpoint needs some filter[...] key to answer at all, as described above, and this is the key the samples send. Substituting the other filter satisfies the endpoint just as well; sending neither is what fails.

filter[scopeOnlyErrors]booleanOptional

Send 1 to return only entries whose status is error. Any falsy value is the same as leaving it out.

searchstring or arrayOptional

The column to search, and the companion to search_term — neither does anything without the other. Send one bare column name, or repeat the parameter to search several: search[]=endpoint&search[]=status. Do not comma-separate them. The value is never split, so search=endpoint,status is looked up as a single column named "endpoint,status", matches nothing, and returns 500 — the same failure as naming a column that does not exist. The searchable columns are type, domain_id, domain_type, uri, method, endpoint, status, request, response, duration, context and meta. Note id, created_at and updated_at cannot be searched.

search_termstringOptional

The text to look for in the columns named by search. Matching is a case-insensitive substring, and a space stands for any run of characters, so voice call also finds voice/call.

sortstringOptional

The only sortable field is created_at; prefix it with - for newest first. There is no default, so results are unordered until you ask. Any other value returns 400.

Allowed values:created_at-created_at
pageintegerOptional

Which page of results to return. It only takes effect alongside limit, because that is the parameter that turns pagination on; sent on its own it is ignored.

limitintegerOptional

How many entries per page, and the switch that turns pagination on. There is no default and no maximum: omit it and the entire history comes back in one bare array. Sent empty it means 24, and limit=0 means 15. Nothing about it is validated.

without_paginationbooleanOptional

Send 1 alongside limit to get the capped result as a bare array rather than a paginated object — useful when you want the last N entries and no envelope to unwrap. Ignored without limit.

cURL
curl --request GET \
  --url 'https://app.wiseparts.ai/api/v1/voice?filter%5BscopeBetween%5D=2026-08-01%2C2026-08-13' \
  --header 'Authorization: Bearer REPLACE_BEARER_TOKEN'
JavaScript
const options = {method: 'GET', headers: {Authorization: 'Bearer REPLACE_BEARER_TOKEN'}};

fetch('https://app.wiseparts.ai/api/v1/voice?filter%5BscopeBetween%5D=2026-08-01%2C2026-08-13', options)
  .then(response => response.json())
  .then(response => console.log(response))
  .catch(err => console.error(err));
PHP
<?php

$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://app.wiseparts.ai/api/v1/voice?filter%5BscopeBetween%5D=2026-08-01%2C2026-08-13",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer REPLACE_BEARER_TOKEN"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
Python
import requests

url = "https://app.wiseparts.ai/api/v1/voice"

querystring = {"filter[scopeBetween]":"2026-08-01,2026-08-13"}

headers = {"Authorization": "Bearer REPLACE_BEARER_TOKEN"}

response = requests.request("GET", url, headers=headers, params=querystring)

print(response.text)
Go
package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {

    url := "https://app.wiseparts.ai/api/v1/voice?filter%5BscopeBetween%5D=2026-08-01%2C2026-08-13"

    req, _ := http.NewRequest("GET", url, nil)

    req.Header.Add("Authorization", "Bearer REPLACE_BEARER_TOKEN")

    res, _ := http.DefaultClient.Do(req)

    defer res.Body.Close()
    body, _ := ioutil.ReadAll(res.Body)

    fmt.Println(res)
    fmt.Println(string(body))

}
Ruby
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://app.wiseparts.ai/api/v1/voice?filter%5BscopeBetween%5D=2026-08-01%2C2026-08-13")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer REPLACE_BEARER_TOKEN'

response = http.request(request)
puts response.read_body

Responses

200

The account's Voice API request history — a bare array of entries unless limit is set, in which case an object with data, links and meta.

400

The sort value names a field this endpoint cannot sort by. Only created_at and -created_at are accepted.

401

The token is missing, wrong, or belongs to an integration that is not active. Requests that fail here leave no entry in the request history, because the account could not be identified. POST /voice/call and /voice/priority always answer with this JSON body. GET /voice only does so when the request asks for JSON — send Accept: application/json or you will get a 302 redirect to a browser page instead of a 401, which is a confusing thing to debug from a script.

500

Everything this endpoint fails to validate arrives here as the same opaque body. The known causes: no filter parameter at all; a filter[scopeBetween] value that is empty or is not a date; a search value that is comma-separated or otherwise names no searchable column; a filter key that is neither a known scope nor a real column; and filter sent as a bare value rather than as bracketed keys.

Response fields

idstring

The entry's own identifier, a ULID.

typestring

Always voice for entries returned here.

domain_idinteger

The account the request belonged to — always your own.

domain_typestring

The kind of owner the entry belongs to. Always wholesalers.

domainobject

The account the entry belongs to.

uristring

The URL the request was made to, without its query string and truncated at 250 characters.

endpointstring

The path portion of the same URL.

methodstring

The verb the request used.

statusstring

success when WiseParts answered with a 2xx, error for anything else. It reflects the HTTP response only — an event that was accepted and then skipped still reads success.

Allowed values:successerror
durationstring

How long WiseParts took to answer, in seconds to two decimal places. Returned as a string, not a number.

requestobject

The request as WiseParts received it. This is an envelope rather than the payload itself — what you sent is under body. Treat an entry as containing live credentials. Only the query string of uri is masked: the Authorization header is stored as it arrived, and on a GET the api_token turns up inside body, because for a GET the query string and the body are merged before they are recorded.

responseobject

WiseParts' answer to the request.

metaobject or null

Always null for Voice entries.

contextstring or null

Always null for Voice entries.

created_atstring

When the request arrived, as YYYY-MM-DD HH:MM:SS in the account's timezone. There is no offset in the value and it is not RFC 3339, so parse it with the account's timezone in mind.

updated_atstring

When the entry was last written, in the same format as created_at.

200 unpaginated
[
  {
    "id": "01K2C8ZP4W7XG5V0RQF3NHTB6A",
    "type": "voice",
    "domain_id": 42,
    "domain_type": "wholesalers",
    "domain": {
      "id": 42,
      "name": "Acme Parts"
    },
    "uri": "https://app.wiseparts.ai/api/v1/voice/call",
    "endpoint": "api/v1/voice/call",
    "method": "POST",
    "status": "success",
    "duration": "0.08",
    "request": {
      "method": "POST",
      "uri": "https://app.wiseparts.ai/api/v1/voice/call",
      "version": "HTTP/HTTP/1.1",
      "headers": {
        "Content-Type": "application/json",
        "Accept": "application/json"
      },
      "body": {
        "id": "CALL-2026-08-12-0042",
        "direction": "inbound",
        "status": "answered",
        "from": "+351210000000"
      }
    },
    "response": {
      "status_code": 200,
      "version": "HTTP/1.1",
      "headers": {
        "Content-Type": "application/json",
        "X-Centrix-Request-Type": "voice_standard"
      },
      "body": {
        "result": "4HWYBVSKu4spptgZG9BKMszhlIWqlgp1bAJGtvUKpDHTSzjs"
      }
    },
    "meta": null,
    "context": null,
    "created_at": "2026-08-12 14:33:07",
    "updated_at": "2026-08-12 14:33:07"
  }
]
200 paginated
{
  "data": [],
  "links": {
    "first": "https://app.wiseparts.ai/api/v1/voice?page=1",
    "last": "https://app.wiseparts.ai/api/v1/voice?page=7",
    "prev": null,
    "next": "https://app.wiseparts.ai/api/v1/voice?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "to": 50,
    "last_page": 7,
    "per_page": 50,
    "total": 312,
    "path": "https://app.wiseparts.ai/api/v1/voice",
    "links": [
      {
        "url": null,
        "label": "&laquo; Previous",
        "page": null,
        "active": false
      },
      {
        "url": "https://app.wiseparts.ai/api/v1/voice?page=1",
        "label": "1",
        "page": 1,
        "active": true
      }
    ]
  }
}
400
{
  "message": "Requested sort(s) `duration` is not allowed. Allowed sort(s) are `created_at`."
}
401
{
  "message": "Unauthenticated."
}
500 missing-filter
{
  "message": "Server Error"
}
500 bad-search
{
  "message": "Server Error"
}