List Recordings & Uploads Endpoint
curl --request GET \
--url https://app.tuco.ai/api/attachments/list \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.tuco.ai/api/attachments/list"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.tuco.ai/api/attachments/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.tuco.ai/api/attachments/list",
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 <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/attachments/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.tuco.ai/api/attachments/list")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/attachments/list")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"recordings": [
{
"url": "<string>",
"name": "<string>",
"size": 123,
"uploadedAt": "<string>",
"pathname": "<string>",
"isAudio": true,
"kind": "<string>",
"callId": "<string>",
"toE164": "<string>",
"fromE164": "<string>",
"durationSec": 123
}
],
"recordingsNextCursor": "<string>",
"recordingsHasMore": true,
"items": [
{
"url": "<string>",
"name": "<string>",
"size": 123,
"uploadedAt": "<string>",
"pathname": "<string>",
"isAudio": true,
"kind": "<string>"
}
],
"nextCursor": "<string>",
"hasMore": true
}Voice / Calling
List Recordings & Uploads Endpoint
List every call recording and uploaded file in your workspace, newest first, with signed playback links.
GET
/
api
/
attachments
/
list
List Recordings & Uploads Endpoint
curl --request GET \
--url https://app.tuco.ai/api/attachments/list \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.tuco.ai/api/attachments/list"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.tuco.ai/api/attachments/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.tuco.ai/api/attachments/list",
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 <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/attachments/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.tuco.ai/api/attachments/list")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/attachments/list")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"recordings": [
{
"url": "<string>",
"name": "<string>",
"size": 123,
"uploadedAt": "<string>",
"pathname": "<string>",
"isAudio": true,
"kind": "<string>",
"callId": "<string>",
"toE164": "<string>",
"fromE164": "<string>",
"durationSec": 123
}
],
"recordingsNextCursor": "<string>",
"recordingsHasMore": true,
"items": [
{
"url": "<string>",
"name": "<string>",
"size": 123,
"uploadedAt": "<string>",
"pathname": "<string>",
"isAudio": true,
"kind": "<string>"
}
],
"nextCursor": "<string>",
"hasMore": true
}This endpoint is in beta. The interface is stable enough to build on; we may add fields as it matures.
Overview
Returns two separate lists for your workspace:recordings— audio from calls placed on your Tuco line, captured automatically.items— files you uploaded, via Upload attachment or the Leads → Upload audio etc tab.
Making a request
- Method:
GET - URL:
https://app.tuco.ai/api/attachments/list - Headers:
Authorization: Bearer tuco_...
curl "https://app.tuco.ai/api/attachments/list?limit=20" \
-H "Authorization: Bearer tuco_your_key"
Query parameters
number
Max entries to return per list. Default
20, min 1, max 100.string
Opaque cursor from a previous response’s
nextCursor, to page through uploads.string
Opaque cursor from a previous response’s
recordingsNextCursor, to page through
recordings. Recordings and uploads paginate independently — see
Paging.Response
{
"recordings": [
{
"url": "https://app.tuco.ai/api/attachments/crm-media/6aac0729....mp3?u=...&exp=...&sig=...",
"name": "Call → +15145550123 · 3:40",
"size": 878733,
"uploadedAt": "2026-09-17T15:28:41.632Z",
"pathname": "workspaces/org_.../recordings/6aac0729....mp3",
"isAudio": true,
"kind": "recording",
"callId": "6aac072927e54a4003b72623",
"toE164": "+15145550123",
"fromE164": "+13025550999",
"durationSec": 220
}
],
"recordingsNextCursor": null,
"recordingsHasMore": false,
"items": [
{
"url": "https://app.tuco.ai/api/attachments/crm-media/vn.m4a?u=...&exp=...&sig=...",
"name": "vn.m4a",
"size": 45056,
"uploadedAt": "2026-09-18T10:02:11.000Z",
"pathname": "workspaces/org_.../attachments/....m4a",
"isAudio": true,
"kind": "upload"
}
],
"nextCursor": null,
"hasMore": false
}
array
Call recordings, newest first.
Show recording fields
Show recording fields
string
Signed playback/download URL. See Links expire.
string
Display label, e.g.
Call → +15145550123 · 3:40.number
File size in bytes.
string
When the call was answered; falls back to when it was requested, then to when the audio was stored.
string
Internal storage path. Informational only.
boolean
Always
true for recordings.string
Always
recording. Use this to tell the two lists apart if you merge them.string
The call’s ID — pass it to your own systems, or match it against Get call recordings.
string
Number that was called.
null if the call record is unavailable.string
Caller ID the lead saw.
null if the call record is unavailable.number
Talk time in seconds.
null if the call record is unavailable.string
Cursor for the next page of recordings, or
null.boolean
Whether more recordings exist beyond this page.
array
string
Cursor for the next page of uploads, or
null.boolean
Whether more uploads exist beyond this page.
Paging
Recordings and uploads are paged independently, each with its own cursor. PassrecordingsCursor to advance recordings and cursor to advance uploads; you can
send both, either, or neither.
# next page of recordings only
curl "https://app.tuco.ai/api/attachments/list?limit=20&recordingsCursor=CURSOR_FROM_LAST_RESPONSE" \
-H "Authorization: Bearer tuco_your_key"
limit applies to each list, so limit=20 can return up to 20 recordings and
up to 20 uploads in one response.Links expire
Every
url is a signed link that expires. Do not store it — keep the callId
(for recordings) or pathname, and call this endpoint again when you need fresh
links. The audio itself is kept indefinitely; only the link is time-limited.Format
Call recordings are MP3, mono — both sides of the conversation are mixed into a single channel. Good for playback and transcription; not suited to separating the speakers.Scope
You only ever see your own workspace’s recordings and uploads. The API key you authenticate with determines the workspace, and there is no parameter to query another one.Errors
| HTTP | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 500 | Unexpected server error — the response body is { "error": "Could not load your uploads. Please try again." } |