Integrate without an SDK
Send product events and read feature flags from any server over HTTP: two addresses, one key per purpose, stable IDs, envelope-preserving retries and an independent exposure.
On this page
Any server can integrate feature flags and product events with plain HTTP, whether its language has no Anectico SDK, you do not want one, or the SDK path does not cover it yet. Your agent writes the calls in your repository and runs your application. Anectico supplies the routes and the evidence reads.
One recipe was tested: a small checkout service in Ruby 2.6.10 using only its standard library. Other languages follow the same calls, but only this one was run, so say so when you hand it back. The code is in Runnable integration examples.
Addresses and keys by purpose
Each address and key has one job. A key that reads cannot send, and a key that sends cannot read.
| Purpose | Address | Credential | Route |
|---|---|---|---|
| Flag decisions | The control address, for example https://app.anectico.com |
A project-bound management key with only flags:read, in server secret configuration |
POST /api/v1/decide |
| Events, identify, account and flag exposures | The capture address, for example https://api.anectico.com |
A server_capture key with only analytics:write |
POST /api/v1/capture |
| Existing OpenTelemetry telemetry | The capture address | A native_ingest key with only ingest:write |
/v1/traces, /v1/logs, /v1/metrics |
| Verification and the agent's own work | The control address | The agent's management key, never held by the application | POST /api/v1/instrumentation/verify |
Send a key in X-Anectico-API-Key; X-API-Key and Authorization: ApiKey are also accepted. Read
both addresses and both keys from the application's configuration. Do not default an address, do not
fall back from one key to another, and refuse to start when a setting is missing or both keys are the
same string. The flags key reads every flag definition of the project, so it never goes to a browser,
a mobile app, a log or a repository. See Native intake keys.
Send an event
Choose the message_id and the timestamp before the first send, derived from something your test
controls. Put the account in the event's $groups. The identity comes from your server's own
session, never from the request body.
{
"events": [
{
"event": "checkout_completed",
"distinct_id": "user_8842",
"session_id": "sess_7d1c",
"message_id": "run-42:checkout_completed",
"timestamp": "2026-10-15T09:30:12.123Z",
"properties": { "cart_value": 129, "$groups": { "company": "acme-corp" } }
}
]
}
curl --fail-with-body -X POST "$ANECTICO_CAPTURE_URL/api/v1/capture" \
-H "X-Anectico-API-Key: $ANECTICO_CAPTURE_API_KEY" \
-H "Content-Type: application/json" \
--data @event.json
session_id is optional; send it when your application owns the browsing session. Identifiers are
printable and at most 200 bytes.
Identify, link, profile and account
| Moment | Event |
|---|---|
| Login | {"event": "identify", "distinct_id": "user_8842", "anon_distinct_id": "visitor_19", "set": {"plan": "pro"}, "message_id": ..., "timestamp": ...} links the visitor's earlier events to the person and updates the profile |
| Account | {"event": "$groupidentify", "distinct_id": "user_8842", "group_type": "company", "group_key": "acme-corp", "message_id": ..., "timestamp": ...} records the account |
| Profile fields | set, set_once and unset on an event update the canonical customer; null in set is a value, not a deletion |
identify and $groupidentify store no event row, so no verification can select them: their
outcome is the acknowledgement. See Direct customer-property
updates and Identify
customers.
Read the acknowledgement, and retry safely
HTTP 200 means a batch ledger came back, not that the event is stored. Read every item:
| Item | Meaning | Action |
|---|---|---|
CAPTURE_DELIVERY_QUEUED |
Accepted for processing; not proof of storage or of a finished identity update | Never retry |
CAPTURE_DELIVERY_REJECTED |
Refused (INVALID_EVENT or QUOTA_EXCEEDED) |
Final for that input; fix it, do not loop |
CAPTURE_DELIVERY_UNKNOWN, HTTP 429 or 503, a timeout or a broken connection |
Uncertain | Back off and resend the same bytes, with the same message ID, a bounded number of times |
Serialize the body once and resend those bytes. Reusing a message ID with different content refuses that event, so never rebuild an event for a retry. A 2xx with a body you cannot read is uncertain, not delivered. See Capture acknowledgements.
Flags: decide, serve, then record the exposure
POST /api/v1/decideto the control address with the flags key, the person'sdistinct_idand the explicitperson_propertiesthe flag's rules use. The answer carriesfeature_flags,project_id,feature_flag_versionsand, per flag,feature_flag_resultswith the evaluation status, whether the value is a fallback, and the input context.- Serve the branch from the value.
falsefrom a successful decision is a value. A failed request, a non-200 answer or a missing flag is an unavailable decision: serve the safe default and send no exposure. - Independently, send a
$feature_flag_calledevent to the capture address with the capture key. Copy the decision's own fields into the reserved properties, never compute a version, never write an unknown fallback asfalse, and leave absent context fields absent. The properties and the input binding the application computes are on Feature-flag exposure verification.
Local evaluation from the snapshot is not covered over plain HTTP: it needs the SDK's frozen bucketing and comparison rules.
Existing OpenTelemetry telemetry
If you already export OTLP, keep it: point the exporter at the capture address with a native_ingest
key and set anectico.distinct_id or trusted W3C baggage at a boundary your server controls. See
OpenTelemetry. An OTLP attribute does not create or link an identity:
that is the identify event above. Checking traces and logs with the exact verification reads is
not covered yet.
Prove it
The agent selects the samples whose delivery was queued and asks for the product_events and flags
jobs, as in Integration job verification. What a
verified sample does and does not establish, and the limits, are on What integration verification
proves and what it does not.
Other jobs over HTTP, and what is not offered
| Job | Where it is documented | Part of this verified workflow |
|---|---|---|
| Survey delivery | Surveys | Not yet |
| Experiment delivery | Product experiments | Not yet |
| Browser session replay behind a backend in any language | Record session replay safely | Not yet |
| Native structural replay | iOS, Android, React Native and Flutter setup | Outside these server examples; use the platform recorder or adapter |
| Replay over OTLP | Not offered | No |
Browser replay uses the JavaScript recorder. Native structural replay has its own format, consent, privacy policy and upload credential. OTLP carries neither recording format.