Ingest

Every write endpoint: push events, create records, fix, remove, undo.

Events

One event: validated against its source pack, committed whole or refused whole naming the field.

Body
{
  "source": "stripe",
  "content": {
    "text": "Invoice INV-4021 paid, $12,400",
    "metadata": { "event_type": "invoice.paid", "amount_paid_cents": 1240000 }
  },
  "identityLinks": [{ "externalIdType": "domain", "externalIdValue": "piedpiper.com" }],
  "timestamp": "2026-08-11T09:14:02Z"
}
curl -s -X POST "$ACKDB_URL/ingest" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "source": "stripe", "content": { "text": "Invoice INV-4021 paid, $12,400", "metadata": { "event_type": "invoice.paid", "amount_paid_cents": 1240000 } }, "identityLinks": [{ "externalIdType": "domain", "externalIdValue": "piedpiper.com" }], "timestamp": "2026-08-11T09:14:02Z" }'
Response · 200
{
  "data": {
    "entityId": "01HXYZ123456789ABCDEFGH",
    "contentId": "01HXYZ123456789ABCDEFGI",
    "created": true,
    "identityLinksCreated": 1,
    "traitsWritten": 1
  }
}
  • Resolution, first hit wins: domain link (matches or creates) → source ids (match only) → universal ids → mintable anchor → explicit entityId → unresolved pool. A matching link beats entityId. Identity.
  • Three non-error outcomes: created, deduplicated: true (idempotency key already stored — keys are global, not per company), unresolvedContentId (nothing matched).
  • Static facts never go through ingest — attributes refuse with 422. Build.
  • Field-by-field walkthrough: Push events.

Batch ingest (up to 500 items)

Body
{
  "source": "slack",
  "items": [
    {
      "content": {
        "text": "…",
        "metadata": {}
      },
      "timestamp": "2026-08-01T00:00:00Z",
      "idempotencyKey": "…",
      "contactId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14",
      "personId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14"
    }
  ],
  "identityLinks": [
    {
      "entityId": "01HXYZ123456789ABCDEFGH",
      "source": "slack",
      "externalIdType": "slack_channel_id",
      "externalIdValue": "C04ABC",
      "confidence": 1,
      "linkedAt": "2026-08-01T00:00:00Z"
    }
  ],
  "entityId": "…",
  "traits": [
    {
      "traitKey": "slack_message_count",
      "value": 42,
      "op": "increment",
      "workflow": "slack-ingestion",
      "confidence": 1
    }
  ],
  "contacts": [
    {
      "contactId": "01JQC7X8Y9Z0A1B2C3D4E5F6G7",
      "email": "…",
      "name": "…",
      "title": "…",
      "phone": "…",
      "department": "…",
      "linkedinUrl": "…",
      "isExternal": true,
      "identityLinks": [
        {
          "externalIdType": "slack_user_id",
          "externalIdValue": "U07ABC123"
        }
      ],
      "traits": [
        {
          "traitKey": "slack_message_count",
          "value": 42,
          "op": "increment",
          "workflow": "slack-ingestion",
          "confidence": 1
        }
      ]
    }
  ]
}
curl -s -X POST "$ACKDB_URL/ingest/batch" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "source": "slack", "items": [ { "content": { "text": "…", "metadata": {} }, "timestamp": "2026-08-01T00:00:00Z", "idempotencyKey": "…", "contactId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14", "personId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14" } ], "identityLinks": [ { "entityId": "01HXYZ123456789ABCDEFGH", "source": "slack", "externalIdType": "slack_channel_id", "externalIdValue": "C04ABC", "confidence": 1, "linkedAt": "2026-08-01T00:00:00Z" } ], "entityId": "…", "traits": [ { "traitKey": "slack_message_count", "value": 42, "op": "increment", "workflow": "slack-ingestion", "confidence": 1 } ], "contacts": [ { "contactId": "01JQC7X8Y9Z0A1B2C3D4E5F6G7", "email": "…", "name": "…", "title": "…", "phone": "…", "department": "…", "linkedinUrl": "…", "isExternal": true, "identityLinks": [ { "externalIdType": "slack_user_id", "externalIdValue": "U07ABC123" } ], "traits": [ { "traitKey": "slack_message_count", "value": 42, "op": "increment", "workflow": "slack-ingestion", "confidence": 1 } ] } ] }'
Response · 200
{
  "data": {
    "entityId": "…",
    "created": true,
    "accepted": 0,
    "duplicated": 0,
    "total": 0,
    "identityLinksCreated": 0,
    "traitsWritten": 0,
    "contactsProcessed": 0
  }
}

Batch differs from single ingest: no unresolved fallback (422 instead), no files[], one bad item rejects all with its 0-based index.

Query ingest logs

Parameters
entityIdFilter by entity ID
sourceFilter by source type
statusFilter by status
sinceStart of date range
untilEnd of date range
limit50
offset0
curl -s "$ACKDB_URL/ingest/logs?limit=50&offset=0" \
  -H "Authorization: Bearer $ACKDB_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "…",
      "endpoint": "/ingest",
      "source": "slack",
      "status": "success",
      "entityId": "…",
      "entityCreated": true,
      "resolutionPath": "domain_match",
      "idempotencyKey": "…",
      "requestSizeBytes": 0,
      "contentId": "…",
      "eventId": "…",
      "identityLinksCreated": 0,
      "traitsWritten": 0,
      "contactsProcessed": 0
    }
  ],
  "pagination": {
    "total": 0,
    "limit": 0,
    "offset": 0
  }
}

Records

Create a company (direct record insert)

Body
{
  "domain": "acme.com",
  "name": "Acme Corp",
  "actor": "vansh@floqer.com"
}
curl -s -X POST "$ACKDB_URL/entities" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "acme.com", "name": "Acme Corp", "actor": "vansh@floqer.com" }'
Response · 200
{
  "data": {
    "entityId": "01JZK3...",
    "name": "Acme Corp",
    "domain": "acme.com",
    "anchor": {
      "type": "domain",
      "value": "acme.com"
    },
    "created": false
  }
}

Create or upsert a contact (person-first)

Parameters
entityIdrequiredCompany id.
Body
{
  "email": "elly@acme.com",
  "name": "Elly Chen",
  "title": "VP Engineering",
  "phone": "…",
  "department": "…",
  "linkedinUrl": "https://www.linkedin.com/in/ellychen",
  "avatarUrl": "https://media.licdn.com/dms/image/v2/abc/profile.jpg",
  "source": "…",
  "identityLinks": [
    {
      "externalIdType": "…",
      "externalIdValue": "…"
    }
  ],
  "traits": [
    {}
  ]
}
curl -s -X POST "$ACKDB_URL/entities/{entityId}/contacts" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "elly@acme.com", "name": "Elly Chen", "title": "VP Engineering", "phone": "…", "department": "…", "linkedinUrl": "https://www.linkedin.com/in/ellychen", "avatarUrl": "https://media.licdn.com/dms/image/v2/abc/profile.jpg", "source": "…", "identityLinks": [ { "externalIdType": "…", "externalIdValue": "…" } ], "traits": [ {} ] }'
Response · 201
{
  "data": "…"
}

Create a person by hand

Body
{
  "name": "Elly Chen",
  "avatarUrl": "https://media.licdn.com/dms/image/v2/abc/profile.jpg",
  "keys": [
    {
      "externalIdType": "email_address",
      "externalIdValue": "elly@newco.com"
    }
  ]
}
curl -s -X POST "$ACKDB_URL/persons" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Elly Chen", "avatarUrl": "https://media.licdn.com/dms/image/v2/abc/profile.jpg", "keys": [ { "externalIdType": "email_address", "externalIdValue": "elly@newco.com" } ] }'
Response · 201
{
  "data": "…"
}

List unresolved content

Parameters
sourceFilter by source type (e.g.
sinceFilter records created after this ISO date
untilFilter records created before this ISO date
curl -s "$ACKDB_URL/unresolved" \
  -H "Authorization: Bearer $ACKDB_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "01HXYZ123456789ABCDEFGH",
      "source": "slack",
      "identityLinks": [
        {
          "externalIdType": "domain",
          "externalIdValue": "C04ABC",
          "confidence": 1
        }
      ],
      "text": "…",
      "metadata": {},
      "traits": [
        {
          "traitKey": "slack_message_count",
          "value": 42,
          "op": "increment",
          "workflow": "slack-ingestion",
          "confidence": 1
        }
      ],
      "rawPayload": {},
      "timestamp": "2026-08-01T00:00:00Z",
      "createdAt": "2026-08-01T00:00:00Z"
    }
  ]
}

Resolve unresolved content to an entity

Body
{
  "unresolvedContentId": "01HXYZ123456789ABCDEFGH",
  "entityId": "01HXYZ123456789ABCDEFGI"
}
curl -s -X POST "$ACKDB_URL/entities/resolve" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "unresolvedContentId": "01HXYZ123456789ABCDEFGH", "entityId": "01HXYZ123456789ABCDEFGI" }'
Response · 200
{
  "data": {
    "entityId": "01HXYZ123456789ABCDEFGI",
    "contentId": "01HXYZ123456789ABCDEFGJ",
    "created": false,
    "identityLinksCreated": 1,
    "traitsWritten": 2
  }
}

On resolve, entityId: null always mints a new company — look the id up first with GET /entities/lookup and pass the match.

Fix

Merge two duplicate entities

Body
{
  "winnerId": "01HXYZ123456789ABCDEFGH",
  "loserId": "01HABCD123456789EFGHIJK"
}
curl -s -X POST "$ACKDB_URL/entities/merge" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "winnerId": "01HXYZ123456789ABCDEFGH", "loserId": "01HABCD123456789EFGHIJK" }'
Response · 200
{
  "data": {
    "winnerId": "01HXYZ123456789ABCDEFGH",
    "loserId": "01HABCD123456789EFGHIJK",
    "linksTransferred": 2,
    "traitsTransferred": 3,
    "contactsTransferred": 1,
    "eventsTransferred": 42,
    "rawPayloadsTransferred": 42,
    "contentTransferred": 5,
    "filesTransferred": 0,
    "conversationsTransferred": 3
  }
}

Find and merge all duplicate entities

Body
{
  "apply": true
}
curl -s -X POST "$ACKDB_URL/entities/merge-duplicates" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "apply": true }'
Response · 200
{
  "data": {
    "message": "…",
    "duplicateGroups": 0,
    "applied": true
  }
}

Merge two persons

Parameters
personIdrequiredPerson id (ULID = persons.person_id).
Body
{
  "loserId": "01HABCD123456789EFGHIJK",
  "winnerOverride": "…"
}
curl -s -X POST "$ACKDB_URL/persons/{personId}/merge" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "loserId": "01HABCD123456789EFGHIJK", "winnerOverride": "…" }'
Response · 200
{
  "data": "…"
}

Batch-merge the open candidates (dry-run by default)

Body
{
  "dryRun": true,
  "limit": 0
}
curl -s -X POST "$ACKDB_URL/persons/merge-duplicates" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "dryRun": true, "limit": 0 }'
Response · 200
{
  "data": "…"
}

Note

The two bulk merges arm differently: companies execute on { "apply": true }, persons on { "dryRun": false }. Humans are never merged automatically — conflicts wait in GET /persons/merge-candidates.

Declaratively set all values of one link type on a company

Parameters
refrequiredEntity id (ULID) or domain.
typerequiredThe identity link type (e.g.
Body
{
  "values": [
    "123456789",
    "987654321"
  ]
}
curl -s -X PUT "$ACKDB_URL/entities/{ref}/links/{type}" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "values": [ "123456789", "987654321" ] }'
Response · 200
{
  "data": {
    "externalIdType": "duns_number",
    "values": [
      "123456789",
      "987654321"
    ]
  }
}

Re-parent a mis-attached contact to the right company

Parameters
contactIdrequiredContact id (ULID = contacts.contact_id) — the affiliation edge.
Body
{
  "entityId": "01J8ZE4X1M5Y6Z7A8B9C0D1E2F",
  "reason": "…"
}
curl -s -X POST "$ACKDB_URL/contacts/{contactId}/move" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "entityId": "01J8ZE4X1M5Y6Z7A8B9C0D1E2F", "reason": "…" }'
Response · 200
{
  "data": "…"
}

Re-point a contact to another person (the split primitive)

Parameters
contactIdrequiredContact id (ULID = contacts.contact_id) — the affiliation edge.
Body
{
  "personId": "…"
}
curl -s -X PATCH "$ACKDB_URL/contacts/{contactId}" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "personId": "…" }'
Response · 200
{
  "data": "…"
}

domain is read-only on the links routes (422): it is the record's anchor, managed by resolution and merge. Ingest-time link conflicts never error — first writer wins and the event still lands.

Remove & undo

Ids only, by design — no filter-based mass delete. Every door takes dryRun, reason, actor.

Delete specific events by id (with cascade)

Body
{
  "ids": [
    "01J9ABCDEF0123456789ABCDEF",
    "01J9ZYXWVU9876543210ZYXWVU"
  ],
  "dryRun": true,
  "reason": "duplicate Slack import"
}
curl -s -X DELETE "$ACKDB_URL/data/events" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": [ "01J9ABCDEF0123456789ABCDEF", "01J9ZYXWVU9876543210ZYXWVU" ], "dryRun": true, "reason": "duplicate Slack import" }'
Response · 200
{
  "data": {
    "dryRun": true,
    "deleted": {
      "events": 2,
      "rawPayloads": 2,
      "files": 1,
      "contentChunks": 3,
      "conversations": 1,
      "blobs": 1
    },
    "wouldDelete": {
      "events": 1,
      "rawPayloads": 1,
      "files": 0,
      "contentChunks": 2,
      "conversations": 1,
      "blobs": 0
    },
    "resolved": [
      {
        "id": "…",
        "entityId": "…",
        "source": "…",
        "eventType": "…",
        "summary": "…",
        "timestamp": "2026-08-01T00:00:00Z",
        "linkedRawPayloadId": "…",
        "fileCount": 0
      }
    ],
    "notFound": [
      "…"
    ],
    "retained": [
      {
        "kind": "identity_links",
        "entityId": "…",
        "count": 0,
        "reason": "…"
      }
    ],
    "pendingRebuild": [
      {
        "entityId": "…",
        "source": "…",
        "contentChunksDeleted": 0,
        "conversationsDeleted": 0,
        "payloadsQueuedForReembed": 0
      }
    ],
    "rebuildNote": "…"
  }
}

Delete a company and its entire timeline

Parameters
entityIdrequiredEntity id (ULID) or domain — this door resolves either.
dryRunPreview only.
Body
{
  "reason": "…",
  "actor": "…",
  "dryRun": true
}
curl -s -X DELETE "$ACKDB_URL/entities/{entityId}" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "…", "actor": "…", "dryRun": true }'
Response · 200
{
  "data": {
    "dryRun": true,
    "deletionId": "…",
    "kind": "company",
    "record": {
      "id": "…",
      "label": "…"
    },
    "deleted": {
      "events": 0,
      "rawPayloads": 0,
      "files": 0,
      "contentChunks": 0,
      "conversations": 0,
      "contacts": 0,
      "persons": 0,
      "entities": 0,
      "identityLinks": 0,
      "contactIdentityLinks": 0,
      "personIdentityLinks": 0,
      "traits": 0,
      "memberAssignments": 0,
      "mergeCandidates": 0
    },
    "detached": {
      "events": 0,
      "agentSessions": 0
    },
    "tombstone": {
      "active": true,
      "identifiers": [
        {
          "type": "…",
          "value": "…"
        }
      ]
    },
    "retained": [
      {
        "kind": "…",
        "count": 0,
        "reason": "…"
      }
    ],
    "pendingRebuild": [
      {}
    ]
  }
}

Delete a human (company timelines keep their evidence)

Parameters
personIdrequiredPerson id (ULID).
dryRunPreview only.
Body
{
  "reason": "…",
  "actor": "…",
  "dryRun": true
}
curl -s -X DELETE "$ACKDB_URL/persons/{personId}" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "…", "actor": "…", "dryRun": true }'
Response · 200
{
  "data": {
    "dryRun": true,
    "deletionId": "…",
    "kind": "company",
    "record": {
      "id": "…",
      "label": "…"
    },
    "deleted": {
      "events": 0,
      "rawPayloads": 0,
      "files": 0,
      "contentChunks": 0,
      "conversations": 0,
      "contacts": 0,
      "persons": 0,
      "entities": 0,
      "identityLinks": 0,
      "contactIdentityLinks": 0,
      "personIdentityLinks": 0,
      "traits": 0,
      "memberAssignments": 0,
      "mergeCandidates": 0
    },
    "detached": {
      "events": 0,
      "agentSessions": 0
    },
    "tombstone": {
      "active": true,
      "identifiers": [
        {
          "type": "…",
          "value": "…"
        }
      ]
    },
    "retained": [
      {
        "kind": "…",
        "count": 0,
        "reason": "…"
      }
    ],
    "pendingRebuild": [
      {}
    ]
  }
}

Bulk delete records by id

Body
{
  "kind": "company",
  "ids": [
    "…"
  ],
  "dryRun": true,
  "reason": "…",
  "actor": "…"
}
curl -s -X DELETE "$ACKDB_URL/data/records" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "company", "ids": [ "…" ], "dryRun": true, "reason": "…", "actor": "…" }'
Response · 200
{
  "data": {
    "dryRun": true,
    "kind": "company",
    "requested": 0,
    "deletedCount": 0,
    "failedCount": 0,
    "notFound": [
      "…"
    ],
    "results": [
      {
        "ref": "…",
        "status": "deleted",
        "result": {},
        "error": {
          "code": "…",
          "message": "…"
        }
      }
    ]
  }
}

Purge all ingested data

Parameters
sourcestripeSource type to purge (e.g., slack, stripe).
curl -s -X DELETE "$ACKDB_URL/data/purge?source=stripe" \
  -H "Authorization: Bearer $ACKDB_API_KEY"
Response · 200
{
  "data": {
    "message": "Purged all data",
    "source": "stripe",
    "deleted": {
      "entities": 8,
      "contacts": 24,
      "contactIdentityLinks": 24,
      "contactTraits": 48,
      "identityLinks": 36,
      "traits": 112,
      "events": 112,
      "rawPayloads": 112,
      "entityContent": 112,
      "unresolvedContent": 0,
      "unresolvedContacts": 0,
      "conversations": 0,
      "files": 0,
      "ingestQueue": 0
    }
  }
}

Deleted is moved, not destroyed — every removed row lands in the archive:

List the deletion archive (recently deleted)

Parameters
kind
status
limit50
cursorThe last id of the previous page — ULIDs sort by creation time, so the id IS the cursor.
curl -s "$ACKDB_URL/deletions?limit=50" \
  -H "Authorization: Bearer $ACKDB_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "…",
      "kind": "company",
      "recordId": "…",
      "label": "…",
      "reason": "…",
      "actor": "…",
      "status": "deleted",
      "tombstoneActive": true,
      "identifiers": [
        {
          "type": "…",
          "value": "…"
        }
      ],
      "counts": {
        "events": 0,
        "rawPayloads": 0,
        "files": 0,
        "contentChunks": 0,
        "conversations": 0,
        "contacts": 0,
        "persons": 0,
        "entities": 0,
        "identityLinks": 0,
        "contactIdentityLinks": 0,
        "personIdentityLinks": 0,
        "traits": 0,
        "memberAssignments": 0,
        "mergeCandidates": 0
      },
      "detached": {
        "events": 0,
        "agentSessions": 0
      },
      "createdAt": "2026-08-01T00:00:00Z",
      "restoredAt": "2026-08-01T00:00:00Z",
      "purgedAt": "2026-08-01T00:00:00Z"
    }
  ],
  "nextCursor": "…"
}

Restore a deleted record

Parameters
idrequired
curl -s -X POST "$ACKDB_URL/deletions/{id}/restore" \
  -H "Authorization: Bearer $ACKDB_API_KEY"

Permanently purge the archived copy (the tombstone stays)

Parameters
idrequired
curl -s -X DELETE "$ACKDB_URL/deletions/{id}" \
  -H "Authorization: Bearer $ACKDB_API_KEY"

Lift the tombstone (allow re-creation)

Parameters
idrequired
curl -s -X DELETE "$ACKDB_URL/deletions/{id}/tombstone" \
  -H "Authorization: Bearer $ACKDB_API_KEY"

A record delete freezes its ids as a tombstone: a re-syncing pipeline can't re-mint it — ingest drops or skips and says so in data.tombstoned; deliberate creates get 409 TOMBSTONED_IDENTIFIER. Delete and undo.

Team & ownership

Create a member

Body
{
  "email": "…",
  "name": "…",
  "title": "…",
  "team": "…",
  "managerId": "…",
  "notes": "…",
  "sourceIds": [
    {
      "source": "…",
      "externalIdType": "…",
      "externalIdValue": "…"
    }
  ]
}
curl -s -X POST "$ACKDB_URL/members" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "…", "name": "…", "title": "…", "team": "…", "managerId": "…", "notes": "…", "sourceIds": [ { "source": "…", "externalIdType": "…", "externalIdValue": "…" } ] }'
Response · 200
{
  "data": "…"
}

Assign (or reassign) a member to a company

Parameters
idrequiredCompany reference — an entity id (ULID) or a domain (e.g.
Body
{
  "memberId": "…",
  "email": "…",
  "name": "…",
  "title": "…",
  "isPrimary": true,
  "source": "…"
}
curl -s -X POST "$ACKDB_URL/entities/{id}/members" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "memberId": "…", "email": "…", "name": "…", "title": "…", "isPrimary": true, "source": "…" }'
Response · 201
{
  "data": "…"
}

Get a company's primary owner (DRI)

Parameters
idrequiredCompany reference — an entity id (ULID) or a domain (e.g.
curl -s "$ACKDB_URL/entities/{id}/owner" \
  -H "Authorization: Bearer $ACKDB_API_KEY"
Response · 200
{
  "data": "…"
}

Transfer a member's book to another member

Parameters
idrequiredMember id (ULID = internal_users.id).
Body
{
  "toMemberId": "…",
  "toEmail": "…",
  "name": "…",
  "title": "…",
  "entityIds": [
    "…"
  ],
  "contactIds": [
    "…"
  ],
  "all": true,
  "source": "…"
}
curl -s -X POST "$ACKDB_URL/members/{id}/transfer" \
  -H "Authorization: Bearer $ACKDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "toMemberId": "…", "toEmail": "…", "name": "…", "title": "…", "entityIds": [ "…" ], "contactIds": [ "…" ], "all": true, "source": "…" }'
Response · 200
{
  "data": "…"
}

An unowned account reads as data: null — a coverage gap, not an error. Deactivation is PATCH /members/{id} with isActive: false.