curl --request GET \
--url https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities \
--header 'Authorization: Basic <encoded-value>'import requests
url = "https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"contactId": "68a1f2c89c41d20014b3e955",
"activities": [
{
"id": "68a2b1c09c41d20014b41a10",
"channel": "email",
"status": "replied",
"direction": null,
"campaignId": "68a201559c41d20014b40111",
"subject": "Re: Quick question about Brightpay's Q3 hiring",
"body": {
"contentType": "html",
"content": "<p>Thanks Sam, happy to chat on Thursday.</p>"
},
"sentAt": null,
"receivedAt": "2026-08-21T14:02:11.000Z",
"scheduledAt": null,
"from": {
"name": "Ada Nwosu",
"address": "ada@brightpay.com"
},
"to": [
{
"name": "Sam Rivera",
"address": "sam@acme.com"
}
],
"linkedinUrl": null,
"phoneNumber": null,
"callTranscript": null,
"callRecordingUrl": null,
"callDisposition": null
},
{
"id": "68a2a0114773c751a92de55d",
"channel": "non_campaign_call",
"status": "answered",
"direction": "outbound",
"campaignId": null,
"subject": null,
"body": {
"contentType": "html",
"content": "- Asked for pricing on the 10-seat plan<br>- Sending the deck, following up Thursday"
},
"sentAt": "2026-08-20T16:47:18.434Z",
"receivedAt": null,
"scheduledAt": null,
"from": null,
"to": [],
"linkedinUrl": null,
"phoneNumber": "+14155552671",
"callTranscript": "Ada: Hello? — Sam: Hi Ada, Sam from Acme, is now a bad time?",
"callRecordingUrl": "https://fuse-call-recordings.s3.us-east-1.amazonaws.com/68a1f2c8/68a2a0114773c751a92de55d.wav?X-Amz-Expires=3600&X-Amz-Signature=8a3f0b7c9d2e4f1a6b5c8d7e9f0a1b2c",
"callDisposition": "connected-positive"
},
{
"id": "OmgR3-p-X9mOYqVQgiQ4Hw",
"channel": "linkedin_message",
"status": "sent",
"direction": null,
"campaignId": "68a201559c41d20014b40111",
"subject": null,
"body": {
"contentType": "text",
"content": "Hi Ada, loved your post on payroll automation. Open to a quick chat next week?"
},
"sentAt": "2026-08-19T09:15:00.000Z",
"receivedAt": null,
"scheduledAt": null,
"from": null,
"to": [],
"linkedinUrl": "linkedin.com/in/ada-nwosu",
"phoneNumber": null,
"callTranscript": null,
"callRecordingUrl": null,
"callDisposition": null
}
],
"nextCursor": "3"
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}A contact's activity timeline
The contact’s activity timeline as the app’s activity drawer shows it, newest first: campaign emails sent and replies received, LinkedIn invitations and messages, dialer calls with their transcript, recording, disposition and the notes the rep saved, activity logged by a connected CRM, and campaign steps still scheduled to go out (scheduledAt set, sentAt null). Every entry carries the same keys whatever the channel, so channel tells you which of them are filled: email rows carry subject, body, from and to; LinkedIn rows carry the message text in body and the profile in linkedinUrl; call rows carry phoneNumber, direction, callTranscript, callRecordingUrl, callDisposition, and the call notes — not a message — in body. Page with limit (1-100, default 100) and cursor: copy nextCursor from the previous page and stop when it is null. limit bounds the page of history; the first page can carry a few entries on top of it (campaign steps still scheduled, and LinkedIn activity, neither of which pages on its own), so read activities.length rather than assuming limit. Free to call.
Errors:
404CONTACT_NOT_FOUND— No contact with that id exists.422VALIDATION_FAILED— The path, query or body failed validation;paramnames the offending field andmessagesays why.429RATE_LIMITED— More than 120 requests per minute or 10,000 per day on the contacts bucket, which every /business/contacts endpoint shares; retry after Retry-After.500UNEXPECTED_ERROR— The timeline could not be assembled; quote request_id to support.
Requires one of the following token scopes: contacts.
curl --request GET \
--url https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities \
--header 'Authorization: Basic <encoded-value>'import requests
url = "https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('https://api.tryfuse.ai/api/v1/business/contacts/{contactId}/activities', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"contactId": "68a1f2c89c41d20014b3e955",
"activities": [
{
"id": "68a2b1c09c41d20014b41a10",
"channel": "email",
"status": "replied",
"direction": null,
"campaignId": "68a201559c41d20014b40111",
"subject": "Re: Quick question about Brightpay's Q3 hiring",
"body": {
"contentType": "html",
"content": "<p>Thanks Sam, happy to chat on Thursday.</p>"
},
"sentAt": null,
"receivedAt": "2026-08-21T14:02:11.000Z",
"scheduledAt": null,
"from": {
"name": "Ada Nwosu",
"address": "ada@brightpay.com"
},
"to": [
{
"name": "Sam Rivera",
"address": "sam@acme.com"
}
],
"linkedinUrl": null,
"phoneNumber": null,
"callTranscript": null,
"callRecordingUrl": null,
"callDisposition": null
},
{
"id": "68a2a0114773c751a92de55d",
"channel": "non_campaign_call",
"status": "answered",
"direction": "outbound",
"campaignId": null,
"subject": null,
"body": {
"contentType": "html",
"content": "- Asked for pricing on the 10-seat plan<br>- Sending the deck, following up Thursday"
},
"sentAt": "2026-08-20T16:47:18.434Z",
"receivedAt": null,
"scheduledAt": null,
"from": null,
"to": [],
"linkedinUrl": null,
"phoneNumber": "+14155552671",
"callTranscript": "Ada: Hello? — Sam: Hi Ada, Sam from Acme, is now a bad time?",
"callRecordingUrl": "https://fuse-call-recordings.s3.us-east-1.amazonaws.com/68a1f2c8/68a2a0114773c751a92de55d.wav?X-Amz-Expires=3600&X-Amz-Signature=8a3f0b7c9d2e4f1a6b5c8d7e9f0a1b2c",
"callDisposition": "connected-positive"
},
{
"id": "OmgR3-p-X9mOYqVQgiQ4Hw",
"channel": "linkedin_message",
"status": "sent",
"direction": null,
"campaignId": "68a201559c41d20014b40111",
"subject": null,
"body": {
"contentType": "text",
"content": "Hi Ada, loved your post on payroll automation. Open to a quick chat next week?"
},
"sentAt": "2026-08-19T09:15:00.000Z",
"receivedAt": null,
"scheduledAt": null,
"from": null,
"to": [],
"linkedinUrl": "linkedin.com/in/ada-nwosu",
"phoneNumber": null,
"callTranscript": null,
"callRecordingUrl": null,
"callDisposition": null
}
],
"nextCursor": "3"
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}Authorizations
Your API token (af_…) as the Basic-auth username, with an empty password.
Path Parameters
Id of the contact: the row id from GET /business/lists/{listId}/rows, or the contactId returned by POST /business/contacts/resolve.
Query Parameters
Maximum number of activities per page, 1-100 (default 100). It bounds the page of history; the first page can carry a few entries on top of it (campaign steps still scheduled, and LinkedIn activity, neither of which pages on its own), so read activities.length rather than assuming limit.
1 <= x <= 100Opaque cursor copied from the previous page's nextCursor (max 500 characters). Omit it for the first page.
1 - 500Response
Success
Id of the contact.
One page of the timeline, newest first, with any campaign steps still scheduled pinned at the top. The first page can hold a few entries more than limit.
Show child attributes
Show child attributes
Cursor for the next page; null on the last page.