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
domainlink, 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
domainlink 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
| Surface | Limit |
|---|---|
POST /ingest/batch | 500 items per call, one source, one company |
| Files | 10MB per file by default (FILE_MAX_SIZE_BYTES) |
GET /entities/events | 200 events per page |
GET /entities | 50 per page by default |
| Segment member-ids | 1,000 per page by default, 5,000 max |
DELETE /data/records | 100 records per call |
| Score points | Plus or minus 100 per entry, per-unit slopes capped |
| Request bodies | 30MB on /ingest*, 4MB on logo uploads |
| Rate limits | Dashboard 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.
| Door | Removes |
|---|---|
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/records | Up to 100 records in one call, each independent |
DELETE /data/events | Specific events by id, with cascade |
POST /data/purge | Everything, or one source |
After a delete, three independent levers on /deletions: restore it whole, destroy the archived copy, or unfreeze the identifiers.