listContactActivities
A contact's activity timeline.
A contact's activity timeline (calls, texts, emails, notes, system events), most recent first. Filter with activityTypes (e.g. [CALL, TEXT, EMAIL]) and direction (INBOUND/OUTBOUND); page with limit/offset (default 25, max 50). Each entry inlines its call/text/email/note payload: notes and texts in full, calls with their summary, emails with BOTH snippet (a short preview) and textExtract (the FULL plain-text body). Read textExtract, not snippet, whenever the question is whether something appears in the history: a detail past the preview's first lines would wrongly look absent. textExtract is NULL on the ~3% of emails that arrived HTML-only, whose body this op cannot read AT ALL: null means UNREAD, not empty. Never conclude such an email lacks what you look for; say which emails you could not read and send the user to the contact's timeline in the app. On a NOTE entry, the note's own author { kind: USER | AI, id, name } says who wrote it (the entry's createdByUser is null for a note an AI assistant wrote for a teammate). An UPDATED_TASK entry carries metadata.oldValues (title, endDate, timezone, taskType, participants) plus newValues.endDate: THE UNDO PATH for a task edit, the only durable record of a prior value since Task keeps no history. Reverse an edit by writing oldValues back with updateTask; never call a previous value unrecoverable without having looked. Only tasks WITH a contact get one, one per edit, so the oldest holds the pre-change value. The heavier history read: getContact does not include this feed. "Did they call / call back" reads activityTypes [CALL]: texts and notes never answer it. A NOTE is the team's own record, never a message: never quote one as the contact's text or email.
curl https://server.goliathdata.com/api/v1/call \
-H "Authorization: Bearer $GOLIATH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operationId": "listContactActivities",
"variables": {
"contactId": "<contactId>"
}
}'import { GoliathClient } from '@goliath-data/sdk'
const goliath = new GoliathClient({
apiKey: process.env.GOLIATH_API_KEY!,
baseUrl: 'https://server.goliathdata.com',
})
const result = await goliath.contacts.listContactActivities({
contactId: '<contactId>'
})const res = await fetch('https://server.goliathdata.com/api/v1/call', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GOLIATH_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
operationId: 'listContactActivities',
variables: {
contactId: '<contactId>'
}
}),
})
const { data, errors } = await res.json()limit is clamped to 50.
Variables
The example sends only what the schema requires. Some operations also need one of several optional variables (one filter or another, say); the description above says which.
Default 25.
Default 0.
Response
What comes back under data: a list shows as one entry, and a value as its type.
{
"data": {
"contactQuery": {
"contact": {
"id": "<ID>",
"activities": [
{
"id": "<ID>",
"type": "NOTE_CREATED",
"createdAt": "2026-01-01T00:00:00.000Z",
"isFromTeamMember": true,
"createdByUser": {
"id": "<ID>",
"firstName": "…",
"lastName": "…"
},
"note": {
"id": "<ID>",
"body": "…",
"createdAt": "2026-01-01T00:00:00.000Z",
"author": {
"kind": "USER",
"id": "<ID>",
"name": "…"
}
},
"call": {
"id": "<ID>",
"direction": "INBOUND",
"durationSeconds": 0,
"status": "RINGING",
"summary": "…",
"recordingUrl": "…",
"disposition": "Connected",
"dispositionNote": "…",
"fromPhoneNumber": "…",
"toPhoneNumber": "…",
"createdAt": "2026-01-01T00:00:00.000Z"
},
"text": {
"id": "<ID>",
"message": "…",
"direction": "INBOUND",
"fromPhoneNumber": "…",
"toPhoneNumber": "…",
"createdAt": "2026-01-01T00:00:00.000Z"
},
"email": {
"id": "<ID>",
"subject": "…",
"snippet": "…",
"textExtract": "…",
"fromEmail": "…",
"fromName": "…",
"toEmails": [
"…"
],
"direction": "…",
"internalDate": "2026-01-01T00:00:00.000Z"
},
"metadata": {
"taskId": "…",
"oldValues": {
"title": "…",
"endDate": "…",
"timezone": "…",
"taskType": "…",
"participants": [
"…"
]
},
"newValues": {
"endDate": "…"
}
}
}
]
}
}
}
}Errors
unknown_variableA variable the operation does not declare. A typo is refused rather than silently dropped.
unauthorizedNo bearer header, or the key is invalid or revoked.
insufficient_scopeThe key lacks the scope the operation requires.
forbiddenA referenced record does not exist, was deleted, or is outside your organization. The gateway cannot tell those apart.
Behaviour
A read. It rejects an Idempotency-Key header with 400 bad_request.