Reference
REST API
Use Anectico's public API conventions and find every supported customer resource family.
The public REST API base is:
https://app.anectico.com/api/v1
Use an SDK for telemetry and the Anectico CLI for ordinary investigation or automation. Use REST when building a direct integration. Anectico is in early access, so pin the client and Anectico release you verified when request-schema stability matters.
Authenticate and select a project
Send an API key in X-Anectico-API-Key. X-API-Key and Authorization: ApiKey an_... are also accepted,
while MCP uses bearer authentication. Prefer one documented header per client.
curl --fail-with-body \
-H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
"https://app.anectico.com/api/v1/persons?project_id=$ANECTICO_PROJECT&search=buyer%40acme.example"
For organization-level credentials, many data reads accept project_id. A project-scoped key may
omit a query-string project_id or repeat its exact signed project. Any non-empty conflicting query
value, including one hidden in a repeated parameter, returns a stable 403 permission_denied before
the product handler runs. The requested project is never silently replaced with the key’s project.
The same fail-closed response applies when a non-empty project_id field is malformed in a way the
standard query parser would otherwise discard, such as an unescaped semicolon or invalid percent
escape. A malformed unrelated query field does not change an otherwise exact or omitted selector.
Organization-level credentials retain request-time project selection. Organization identity always
comes from the authenticated principal, not from request input.
Issue reads follow that rule. GET /errors/groups accepts exact platform, service_name,
mechanism, and release occurrence filters alongside project_id, environment, and time bounds.
GET /errors/groups/{group_id}/affected also requires the intended project_id for an
organization-level credential; a missing group and a group owned by another project both return the
same 404.
Resource catalog
| Area | Resource prefixes | Main purpose |
|---|---|---|
| Customers and accounts | /persons, /groups, /group-types, /account-segments |
Profiles, timelines, group accounts, watched segments |
| Raw observability | /traces, /logs, /metrics, /services, /search, /stream/logs |
Search, correlation, aggregation, live logs, RED metrics, service map |
| Errors and delivery context | /errors, /issues, /releases, /sourcemaps, /symbols |
Issue triage, impact, lifecycle, deploys, readable stacks, ticket links |
| Replay | /replay, /replay-for-trace |
Recordings, snapshots, related evidence, trace lookup |
| Events and rollout | /events, /analytics, /flags, /decide |
Event discovery, trends and triggerers, cohorts, flag configuration and evaluation |
| AI execution | /agent-runs, /llm, /evals, /ai |
Runs, scores, cost, pricing overrides, quality settings, natural-language query |
| Detection and response | /anomalies, /alerts, /incidents, /oncall, /escalation-policies |
Findings, rules, silences, response, schedules, routing |
| Saved work and export | /dashboards, /search/saved, /export |
Dashboards and widgets, saved searches, background exports |
| Workspace and access | /projects, /account, /invitations, /settings, /usage, /users/me |
Projects, members, API keys, configuration, usage, contact methods |
| Integrations and delivery | /connections, /notifications, /tickets |
Provider authorization, delivery channels, Issue ticket links |
Organization-level ticket creation requires project_id; every caller supplies an absolute
issue_url and an Idempotency-Key header (or idempotency_key body field). Reusing the key with
the same request returns the existing provider ticket link; reusing it with a different request is
rejected. GET /tickets/{id} and Issue ticket
lists expose truthful sync state and safe retry errors. Every ticket operation resolves to one
explicit project. Organization-level credentials must send one consistent project_id;
project-scoped credentials may omit it or repeat their exact signed project. A different non-empty
query value returns the shared 403 permission_denied before routing. A conflicting create-body
value, when the query is omitted or exact, retains the same generic 404 for
real, foreign, and unknown projects before ticket creation is attempted. Jira/Linear
OAuth start accepts non-secret metadata. Reauthorization also sends the exact existing
connection_id; the callback refreshes that needs_reauth row with an optimistic version check
rather than creating a duplicate. The callback is /api/v1/connections/oauth/callback and returns
to /configure/connections.
| Customer Detective | /investigations | Start, continue, list, and read grounded investigations |
Telemetry ingestion uses OTLP /v1/traces, /v1/logs, and /v1/metrics plus diagnostic-event
/api/v1/capture; it is documented separately because it does not follow normal resource CRUD.
Platform-operator /admin routes and provider callback/webhook routes are not customer integration
surfaces and are intentionally excluded from this catalog.
Event and flag reads
Event discovery and triggerer reads are deterministic, project-scoped pages:
GET /events/names?project_id=&prefix=&property_key=&property_value=&limit=&cursor=returnseventspluspagination.limitis 1–50 when present.GET /events/triggerers?project_id=&event=&since=&until=&property_key=&property_value=&limit=&cursor=requires exact RFC 3339sinceanduntiltimestamps and returns canonical person IDs plus the recognizable distinct ID, withpagination.limitis 1–1,000 when present.POST /events/trendsrequiresproject_id, one or moreevents, andtime_range: {start,end}with exact RFC 3339 bounds. It returns bucketed series forcountorunique_persons.
Malformed or duplicate limit, cursor, since, or until parameters return 400 before a
query runs. Cursors are opaque and bound to the exact project and filters; preserve them unchanged.
A project-scoped principal may omit project_id or repeat its exact project; a conflicting explicit
query value returns 403 permission_denied before an analytics query runs.
POST /decide evaluates flags remotely. GET /flags/local-evaluation?project_id= downloads the
equivalent local-evaluation snapshot. The snapshot body includes the full flag definitions plus
schema_version, snapshot_version, updated_at, max_age_seconds, etag, and project_id.
Its HTTP response also sends ETag: "<etag>" and Cache-Control: private,max-age=<max_age_seconds>.
Send that exact quoted value as If-None-Match; an unchanged snapshot returns 304 with no body.
Validators are bound to their project and must never be reused after switching projects.
Saved searches
Saved searches are isolated to one project. Project-scoped credentials are pinned to their signed
project and may omit project_id or repeat that exact value. A different non-empty query value,
including one hidden in a repeated parameter, returns the shared 403 permission_denied before
routing. A conflicting mutation-body value with an omitted or exact query retains the Saved Search
contract’s same generic 404 for a real sibling project and an unknown project before the service is
called. Organization-level
credentials must send one consistent project_id. Within that project, list/get show the caller’s
private searches plus searches shared by another owner. A hidden private search, an unknown ID, and
an ID copied from another project all return the same 404.
GET /search/saved?project_id=&signal=&limit=&cursor=lists with an opaque, scope-bound cursor.GET /search/saved/{id}?project_id=returns one search and itsversion.POST /search/savedcreates a search; the JSON body requiresproject_idandidempotency_keyfor organization-level callers.PATCH /search/saved/{id}?project_id=requiresexpected_versionin the JSON body.POST /search/saved/{id}/duplicate?project_id=requiresexpected_versionandidempotency_key; the new copy is private to the caller.DELETE /search/saved/{id}?project_id=&expected_version=performs the owner-only delete.
Create and duplicate idempotency is durable. An exact retry replays the original response, while
reusing a key with different arguments returns 409. Stale update/delete/duplicate versions also
return 409 without changing data. A malformed or wrong-scope non-empty cursor returns 400 and
never restarts at page one.
Durable exports
POST /api/v1/export?project_id=creates a CSV, JSON, or Parquet job. Send a stableidempotency_keyin the JSON body orIdempotency-Keyheader. Keys are 1–128 canonical ASCII letters, digits,.,_,:, or-; whitespace, control characters, and Unicode lookalikes are rejected instead of normalized. The opaque query must name a supported signal and may carry RFC 3339 or integral Unix-secondstart_time/end_timevalues.POST /api/v1/logs/export?project_id=is the logs convenience endpoint. Its optionalformatiscsv,json, orparquet, and its time bounds are strict RFC 3339 values.GET /api/v1/export/jobs?project_id=&status=&type=&limit=&cursor=returns newest-first signed keyset pages. Preserve a non-empty cursor unchanged with the same project and filters.GET /api/v1/export/jobs/{id}?project_id=polls lifecycle, monotonic progress, attempt count, and expiry.DELETE /api/v1/export/jobs/{id}?project_id=atomically cancels a pending, running, or retrying job.POST /api/v1/export/jobs/{id}/retry?project_id=re-enqueues a failed job.GET /api/v1/export/jobs/{id}/download?project_id=mints a signed URL only for an unexpired completed result. URL lifetime is capped at one hour and never extends past result expiry. Result objects expire and are deleted 24 hours after completion.
Every export job {id} is the exact lowercase canonical hyphenated UUID returned at creation
(8-4-4-4-12, 36 characters). Uppercase, compact, braced, URN, control/Unicode-contaminated, and
oversized forms return 400 before a database lookup. A well-formed unknown or sibling-project UUID
returns the same generic 404, so UUID validation does not reveal whether a job exists.
Creation requires export:write and the source signal’s read scope. Read/list requires
export:read; download URL minting requires owner/admin export:download. Every customer request
resolves to one verified project in the authenticated organization. Project-scoped credentials
may omit project_id or repeat their exact signed project. A different non-empty URL-query value
returns the shared 403 permission_denied before routing. With an omitted or exact URL query, a
conflicting top-level create value, nested export query, or logs-export body retains Export’s same
generic 404 without checking whether that project exists or calling Export. Organization-level
credentials retain explicit project selection. Sibling-project job IDs return the same 404 as
unknown IDs for read, cancel, retry, and download. Unknown
formats, malformed or reversed times, tampered or foreign cursors, and expired downloads fail
closed.
Direct customer-property updates
SDK identify() is the usual profile-update surface. A CRM or batch importer that
needs explicit source times, initialization, or deletion can send the complete raw
capture wire with an analytics:write key:
{
"events": [
{
"event": "crm_profile_updated",
"distinct_id": "user_8842",
"timestamp": "2026-07-30T12:30:00Z",
"message_id": "crm-contact-8842-v17",
"properties": {"source": "crm", "revision": 17},
"set": {"plan": "enterprise", "locale": null},
"set_once": {"signup_source": "web"},
"unset": ["legacy_region"]
}
]
}
properties belongs to the named product event and appears in its customer
timeline payload. The top-level set, set_once, and unset fields mutate the
canonical customer:
setstores literal JSON values.nullis a present value; it does not delete.unsetis the only deletion operation.- For
set/unset, the greatest(timestamp, message_id)wins independently per key. A lexicographically greater message ID breaks an equal-time tie. set_onceis deterministic initialization. The earliest candidate wins until anyset/unsetexists; an authoritative mutation always supersedes it.
The endpoint defaults an omitted timestamp to receive time and supplies an
internal event ID when message_id is absent, but importers should provide both
so retries and out-of-order delivery converge predictably. A key may appear in
only one mutation field in an event. Empty or duplicate names and more than 250
combined mutation keys return a per-event validation error. unset is rejected
on $groupidentify; v1 account properties do not support deletion.
Before an accepted event is published, Anectico recursively replaces detectable
PII and credentials in properties, set, and set_once string leaves with
[REDACTED], including values inside nested arrays and objects. A field whose
separator-delimited or camel-case name ends in a credential concept (for
example, initial_password, customer.api-token, or privateKey) has its
complete value replaced with one placeholder, including numeric, boolean, null,
object, and array values. Substring near-misses such as tokenizer,
token_count, passwordless, and public_key remain available.
Run, unit-ID, and correlation properties preserve calendar-valid compact timestamp tokens
such as 20260804-195200, even when those digits coincidentally satisfy a card
checksum. The exception is token-specific and key-aware: actual card numbers,
emails, and credentials in the same value are still redacted, ordinary free-text
properties do not receive the exception, and credential-classified properties
are always replaced as a whole.
Stable identity and correlation keys (distinct_id, anon_distinct_id,
session_id, message_id, group_type, group_key, and the exact top-level
properties.$groups map) are not rewritten; they must instead be printable and
at most 200 bytes. A nested $groups field receives the normal redaction policy.
Property keys are at most 512 bytes, string leaves are at most 32,768 bytes, maps
and arrays contain at most 250 entries, and nesting is at most 16 levels. Control
characters are rejected throughout. Event names and property keys whose text
itself contains detectable sensitive material are rejected rather than renamed.
Validation remains best-effort per batch: valid siblings are sanitized and
accepted while each invalid event receives an indexed error, and error/log paths
never echo the rejected property content.
The reserved identify event performs the same canonical-person mutation and
alias linking but remains a rowless control event. Use an ordinary named event,
as above, when the source payload must also be visible in the customer timeline.
Request and response conventions
- Send JSON writes with
Content-Type: application/jsonunless an upload endpoint requires a raw symbol artifact. - JSON field names are
snake_case. - Times are RFC 3339; common windows use
start_timeandend_time. Omitting an optional timestamp selects the endpoint’s documented default, while a non-empty malformed timestamp returns400 invalid_requestinstead of silently selecting that default. GET /persons/{person_id}/timelineacceptsstart_timeandend_timeto bound the merged customer story. Keep those bounds unchanged while following its opaque pagination cursor.- Repeated filters may accept repeated parameters or comma-separated values, depending on the resource.
- Paginated reads commonly accept
limitand an opaquecursor. Return the next cursor unchanged; do not decode or construct it. - Log, trace, Errors, and alert-rule paginated reads—including affected-customer results—reject a
malformed non-empty pagination cursor with
400 InvalidArgument; they never treat it as a request for the first page. - Server limits clamp page sizes. Do not assume one maximum across resources.
- A paginated read reports
has_moretogether with anext_cursor, and sets neither once the page is the last one. Treat a full page withhas_morefalse as the complete result, and keep followingnext_cursorwhilehas_moreis true rather than assuming one page is everything. - Not every listing paginates. A resource documented without
limitandcursor—alert silences and API keys among them—returns every matching record and ignores those parameters. - Use IDs returned by the API for updates and deletes; do not derive them from display names.
Errors and retries
Errors use a JSON envelope:
{
"error": "permission_denied",
"message": "the key does not have the required scope"
}
Treat 400 as invalid input, 401 as a missing or invalid credential, 403 as insufficient scope,
404 as missing or inaccessible, 409 as a state conflict, and 429 as a rate or plan limit. Retry
429 and temporary 5xx responses only with bounded exponential backoff, honoring Retry-After
when present. Do not automatically retry validation or permission failures.
Discover exact operations
The installed CLI mirrors the public surface and can print its current command contract:
anectico docs # JSON manifest for tools and agents
anectico docs --markdown # readable command and flag reference
Use the current CLI manifest or dashboard network contract for exact early-access request bodies. Do not copy internal gRPC or service routes into an external integration.