Outcome
Inspect one discovered prospect contact, including its archived state and active list memberships.
Before you start
- Verify the environment, credential, authorized organization and the published action permission workspace:read.
- Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
Discover the target
- Select contacts[].id from contacts.list, or retain the id returned by an earlier authorized contact operation. Contact search omits archived rows; a retained archived identifier can still be read.
topolo whoami --json
topolo actions get app_topolo_engage.contacts.get --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
id | Yes | Required discovered contact identifier, trimmed non-empty string up to 128 characters. Bind the workspace separately. |
Validate, plan and execute
Set ENGAGE_WORKSPACE_ID to the identifier of your selected workspace. Prepare payload.json from the current input schema and discovered identifiers. Use the same payload for validation, planning and execution.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions validate app_topolo_engage.contacts.get --data @payload.json --json
topolo actions plan app_topolo_engage.contacts.get --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json
Execute the unchanged validated read in the selected workspace.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.contacts.get --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json
For MCP, use topolo_get_action, topolo_validate_action, topolo_plan_action and topolo_call_action. Pass the selected resource context separately; confirm:true is required for mutations.
Worked requests and responses
Captured contacts.get · HTTP 200
Action input
{
"id": "eng_ct_8a74645293e5454cb3655287e9df0a52"
}
Actual local response
{
"contact": {
"id": "eng_ct_8a74645293e5454cb3655287e9df0a52",
"email": "boss@acme.io",
"domain": "acme.io",
"addressType": "person",
"verificationStatus": "unknown",
"firstName": null,
"lastName": null,
"company": null,
"title": null,
"website": null,
"country": null,
"timezone": null,
"source": "manual",
"custom": {},
"createdAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z",
"archivedAt": null
},
"lists": []
}
Source test: reply inbox actions > threads.classify > referral closes and creates a manual, unenrolled contact (reusing an existing one). Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured contacts.get · HTTP 200
Action input
{
"id": "contact_1"
}
Actual local response
{
"contact": {
"id": "contact_1",
"email": "ada@acme.io",
"domain": "acme.io",
"addressType": "person",
"verificationStatus": "unknown",
"firstName": "Ada",
"lastName": null,
"company": "Analytical",
"title": null,
"website": null,
"country": null,
"timezone": null,
"source": "manual",
"custom": {
"tier": "a"
},
"createdAt": "2026-10-01T00:00:00.000Z",
"updatedAt": "2026-10-01T00:00:00.000Z",
"archivedAt": null
},
"lists": []
}
Source test: audience routes > contacts > creates, reads, searches, edits and archives a manual contact. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured contacts.get · HTTP 200
Action input
{
"id": "contact_1"
}
Actual local response
{
"contact": {
"id": "contact_1",
"email": "ada@acme.io",
"domain": "acme.io",
"addressType": "person",
"verificationStatus": "unknown",
"firstName": null,
"lastName": null,
"company": null,
"title": null,
"website": null,
"country": null,
"timezone": null,
"source": "manual",
"custom": {},
"createdAt": "2026-10-01T00:00:00.000Z",
"updatedAt": "2026-10-01T00:00:00.000Z",
"archivedAt": null
},
"lists": [
{
"id": "list_1",
"name": "Founders"
}
]
}
Source test: audience routes > lists > adds and removes members, counting only active contacts. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured contacts.get · HTTP 200
Action input
{
"id": "contact_1"
}
Actual local response
{
"contact": {
"id": "contact_1",
"email": "ada@acme.io",
"domain": "acme.io",
"addressType": "person",
"verificationStatus": "unknown",
"firstName": "Ada",
"lastName": null,
"company": "Analytical",
"title": null,
"website": null,
"country": null,
"timezone": null,
"source": "manual",
"custom": {},
"createdAt": "2026-10-01T00:00:00.000Z",
"updatedAt": "2026-10-01T00:00:00.000Z",
"archivedAt": null
},
"lists": [
{
"id": "list_1",
"name": "L"
}
]
}
Source test: audience routes > imports > updates changed contacts, matches unchanged ones and adds both to the list under a new key. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured contacts.get · HTTP 200
Action input
{
"id": "contact_1"
}
Actual local response
{
"contact": {
"id": "contact_1",
"email": "ada@acme.io",
"domain": "acme.io",
"addressType": "person",
"verificationStatus": "unknown",
"firstName": "Ada",
"lastName": "Lovelace",
"company": "Analytical",
"title": null,
"website": null,
"country": null,
"timezone": null,
"source": "manual",
"custom": {},
"createdAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z",
"archivedAt": null
},
"lists": []
}
Source test: contacts.export > caps a kind at the row limit and names it. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- Returns contact plus lists[] containing active list id/name references ordered by list name then id. The contact may have archivedAt set; get does not filter it out.
- Missing contacts and identifiers outside the selected workspace return not_found. This read does not restore a contact or change memberships.
Verify the result
- Compare contact.id with input id; inspect archivedAt before planning edits or enrollment. An empty lists array only means no active list references were returned, not that archived lists never held this contact.
- Use lists[].id with lists.get for the retained active list metadata.
Recover
Retry this read after a transient failure with the same verified workspace and validated input. Rediscover missing identifiers within that workspace; never create or modify a record to make a read succeed.
Refused responses
Captured contacts.get · HTTP 404
Action input
{
"id": "contact_1"
}
Actual local response
{
"error": {
"code": "not_found",
"message": "Contact was not found in this workspace."
}
}
Source test: audience routes > contacts > returns 404 for a contact in another workspace or organization. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Error recovery
- HTTP 403
permission_denied: Stop and inspect the permission required by this action in the current credential. Do not broaden permissions or switch organization automatically. - HTTP 422
invalid_action_input: Correct the request against the current input schema and validate it again before planning. - HTTP 404
not_found: Rediscover the identifier in the same authorized workspace. An identifier from another workspace is not a valid substitute; retain archived identifiers for direct reads.
Related actions
Evidence and limits
-
contracts/audience.mjs -
contracts/audience-filter.mjs -
apps/api/src/audience/audience-routes.ts -
apps/api/src/audience/contacts-handlers.ts -
apps/api/src/audience/contact-search.ts -
apps/api/src/audience/contacts-repository.ts -
apps/api/src/audience/lists-handlers.ts -
apps/api/src/audience/lists-repository.ts -
apps/api/src/audience/filter-source.ts -
apps/api/test/audience-routes.test.ts -
apps/api/test/segments-routes.test.ts -
Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
-
Widget/bootstrap use actual app handler but mocked worker runtime and app identity.
-
No gateway execution, live permissions, external delivery or deployed proof.
The examples are local route captures with synthetic context and simulated providers. They do not prove live authorization, CLI/MCP gateway execution, production state or delivery.