AckdbIngestRequest

Request body for data ingestion from workflow tools. Content is validated against the source's content_schema, traits against trait_definitions. Ownership comes from the event type: entity is company-only, person is a person at a company, person_only is the human alone. Personal requests omit company links (or send empty arrays), forbid non-null entityId/contactId and nonempty contacts/traits/files, and resolve a durable person key or explicit personId. Personal structured/activity events create no affiliations.

Properties

sourcestringrequired

Source type (must exist in schema registry).

contentobjectrequired

The content payload with text and source-specific metadata.

textstringrequired

Raw text from the source - this is what gets embedded and searched.

metadataobjectrequired

Source-specific fields, validated against the source's content_schema.

identityLinksarray of

At least one company link for entity/person types; omit or empty for person_only.

traitsarray of

Pre-computed trait values. Each is validated against trait_definitions.

entityIdstring | null

Explicit entity ID. If provided and no identity match, uses this entity.

contactIdstring | null

Optional strict affiliation pin for company-associated person events. Omit or null for person_only.

personIdstring

Optional strict human ULID pin. Personal events create no affiliation. Existing person-scope events retain company affiliation resolution.

timestampstring <date-time>required

When this data was created in the source system (not when ingested).

idempotencyKeystring

Optional caller-provided idempotency key. If omitted, ackDB auto-computes one via SHA-256(source + content). Duplicate keys are silently accepted (200 with deduplicated: true). Use this to pass a natural key like a Stripe event ID or Slack message timestamp.

contactsarray of

Optional people to create or update at this entity, in addition to whatever the event's contact mappings extract. This is how a multi-person event (a meeting's attendee list, a group thread) records everyone: the contactId/personId rails above pin ONE actor, this array carries the rest. Each entry resolves independently through the same person-first resolver.

filesarray of

Optional files to upload and attach to this entity. Each file is base64-encoded. Max 10MB per file (configurable via FILE_MAX_SIZE_BYTES). Files are stored in the configured FileStore and processed asynchronously by `npm run process-files` to generate embeddings for semantic search.

Example

AckdbIngestRequest example
{
  "source": "slack",
  "content": {
    "text": "Customer asked about pricing",
    "metadata": {}
  },
  "identityLinks": [
    {
      "externalIdType": "domain",
      "externalIdValue": "C04ABC",
      "confidence": 1
    }
  ],
  "traits": [
    {
      "traitKey": "slack_message_count",
      "value": 42,
      "op": "increment",
      "workflow": "slack-ingestion",
      "confidence": 1
    }
  ],
  "entityId": null,
  "contactId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14",
  "personId": "01KWSVN7YY7Q2Q9QZ8GEHHYW14",
  "timestamp": "2026-03-08T10:00:00Z",
  "idempotencyKey": "stripe:evt_1234567890",
  "contacts": [
    {
      "contactId": "01JQC7X8Y9Z0A1B2C3D4E5F6G7",
      "email": "string",
      "name": "string",
      "title": "string",
      "phone": "string",
      "department": "string",
      "linkedinUrl": "string",
      "isExternal": false,
      "identityLinks": [
        {
          "externalIdType": "slack_user_id",
          "externalIdValue": "U07ABC123"
        }
      ],
      "traits": [
        {
          "$ref": "See AckdbIngestTrait"
        }
      ]
    }
  ],
  "files": [
    {
      "name": "q4-proposal.pdf",
      "type": "application/pdf",
      "sizeBytes": 2048576,
      "content": "string",
      "sourceFileId": "F04ABC123",
      "sourceUrl": "https://files.slack.com/files-pri/T04ABC-F04ABC123/q4-proposal.pdf",
      "metadata": {}
    }
  ]
}