Errors and limits

Match a symptom to its cause: the errors ackDB returns, the limits it enforces, and what to do about each.

The hard edges from every page, collected. Each also appears beside the feature it belongs to.

1. Append-only guarantees

  • Ingest never updates or deletes an existing event. Corrections are new events; removal is an explicit delete below.
  • Every stored row carries provenance: source, timestamps, and the raw payload it came from, kept for evidence and replay.
  • Re-pushing is safe: idempotency keys (yours, or a content hash) make duplicate calls a 200 with deduplicated: true, writing nothing.
  • Derived values (counts, latest values, scores, segment membership) are computed on read and stored nowhere, so they cannot go stale and never need a backfill.

2. What ackDB never does

  • Never connects to a source. No Slack tokens, no Stripe keys; producers push.
  • Never computes or enriches. It stores what your workflow computed, exactly as validated.
  • Never writes back to an external system.
  • Never mints a company from a source-specific id; only a domain link, a registered mintable anchor, or an explicit create can.
  • Never fuses two humans on a guess: a durable person key owned by someone else is a 409 with a merge candidate, resolved deliberately.
  • Never lets a producer decide internal vs external; the deployment's config is the sole authority.

3. Validation edges

  • Refusals name the problem: the missing field, the wrong-typed value, the enum value that is not allowed.
  • A blank domain link is the one footgun validation does not catch today: an empty string mints a junk entity. Omit the link instead.
  • Empty strings are not numbers or dates: "" in a number or date value is a 422. Omit the field.
  • Every value your items assert must be registered first: unknown sources, event fields, and keys are refused, not stored loosely.

4. Limits

SurfaceLimit
POST /ingest/batch500 items per call, one source, one company
Files10MB per file by default (FILE_MAX_SIZE_BYTES)
GET /entities/events200 events per page
GET /entities50 per page by default
Segment member-ids1,000 per page by default, 5,000 max
DELETE /data/records100 records per call
Score pointsPlus or minus 100 per entry, per-unit slopes capped
Request bodies30MB on /ingest*, 4MB on logo uploads
Rate limitsDashboard data reads: 60/min per dashboard. Dashboard previews: 60/min per caller. Nothing else is throttled

5. Deletion

Deletes are real and reversible: the record leaves the live tables, an archived copy is kept, and its identifiers stay frozen so tomorrow's sync cannot re-create what you removed. ?dryRun=true runs the full cascade and rolls back.

live recordspiedpiper.comhooli.comdeleterestorearchived copywhole, restorableidentifiers frozenre-sync cannot re-create
A delete removes the record, keeps a restorable copy, and freezes its identifiers so a re-sync cannot re-create it. Preview any delete with dryRun=true.
DoorRemoves
DELETE /entities/{id}A company, by id or domain, and its whole timeline
DELETE /persons/{id}The human; their events detach and stay on company timelines
DELETE /contacts/{id}One affiliation edge
DELETE /data/recordsUp to 100 records in one call, each independent
DELETE /data/eventsSpecific events by id, with cascade
POST /data/purgeEverything, or one source

After a delete, three independent levers on /deletions: restore it whole, destroy the archived copy, or unfreeze the identifiers.