iOS SDK API
Functions, options, parameters, return values, and propagation helpers for the Anectico Swift SDK.
On this page
This is the public Swift API for the native Anectico iOS SDK. It supports iOS 15 and later.
configure throws AnecticoConfigurationError for invalid options; telemetry calls made before
configuration remain safe no-ops with a log warning. Event and error network work runs off the calling thread.
Use separate project-bound native_ingest and mobile_capture keys. Optional native structural recording has independent controls; see native replay controls.
Intake credentials
Set apiKey to a project-bound native_ingest key (ingest:write) and
captureApiKey to a different mobile_capture key (analytics:write). Replace the
example placeholders with your issued keys. Never bundle server, read or management credentials.
Mobile capture declares a public-client origin; it does not establish trusted server authority.
Without captureApiKey, product events, identify and group and automatic lifecycle events are no-ops
before identity changes or queue admission; OTLP remains available and configuration logs a warning.
Malformed or shared capture keys reject configuration. Missing credentials do not erase queued
product events: the flush result reports pending product positions. Consent withdrawal still purges
the queue and remains in force after a key is added. Logout and reset require available product collection.
See native intake keys for creation and verification.
Configure the SDK
import Anectico
try Anectico.configure(
AnecticoOptions(
apiKey: "an_...",
captureApiKey: "an_<mobile-capture-key>",
environment: "production",
release: "com.acme.Shop@2.4.1+318",
dist: "318"
)
)
| API | Parameters | Returns | Behavior |
|---|---|---|---|
Anectico.configure(options) |
AnecticoOptions |
throws; Void |
Validates options, then configures identity, sessions, event queue, error transport, crash handlers, lifecycle hooks, and timers. If the app is already active, emits its missed $app_opened exactly once. Repeated calls are ignored; calls while shutdown is still draining throw. Ordinary configuration preserves analytics withdrawal across restart. |
Anectico.configureApplication(options) |
ApplicationOptions? |
throws; Void |
Replaces optional flag and survey clients without changing telemetry identity, session or consent. Pass nil to clear both. Old decisions, offers and pending operations are retired; invalid options preserve the current clients. Requires initial SDK configuration. |
Anectico.flush(completion:) |
callback taking TelemetryFlushResult |
Void |
Starts an asynchronous diagnostic-event flush and hands the completion the per-signal delivery outcomes. See Knowing whether your events arrived. |
Anectico.shutdown(completion:) |
callback taking TelemetryFlushResult |
Void |
Stops timers and hooks, flushes queued events, releases state, and permits reconfiguration after its completion fires. The completion carries the final flush's outcome, like flush. |
Anectico.disableAnalytics() |
— | Bool |
Stops analytics admission and retries and persists withdrawal. true confirms the withdrawal marker and queue purge; see below for failure handling. |
Anectico.configure(options, resumeAnalyticsCollection: true) |
AnecticoOptions; explicit renewed consent |
throws; Void |
After shutdown completion, purges withdrawn events before clearing the marker and creating a fresh client. |
Anectico.stats() |
— | AnalyticsStats? |
Delivery counters — recorded, delivered, dropped, queued — or nil before configure. |
Anectico.onDeliveryError(_:) |
handler taking DeliveryError, or nil |
Void |
Registers a handler called once per permanently discarded batch with status, dropped, and reason. Safe to call before configure. |
Knowing whether your events arrived
Anectico.flush and Anectico.shutdown return a per-signal TelemetryFlushResult through the
native completion or bridge future. Read productEvents, logs and traces: each has accepted,
rejected, pending, unknown, sampled, warnings, status and nullable unavailableReason.
Logs are null on iOS, which has no public log producer. Traces include errors, messages, crashes
and agent-run spans. Pending is the queue and spool snapshot; the other counts describe outcomes since
the previous completed public flush or shutdown. An empty accepted queue with count zero proves
no delivery. Read every count: pending work can coexist with a refused sibling.
Local queue eviction and readable expired or excess error payloads count as rejected records. Pending counts readable records rather than spool files. Corrupt payloads and failed directory listing report unknown with an unavailable reason; zero pending then does not prove an empty queue. A durable pre-send claim excludes each batch from restart replay. Interrupted claims are quarantined as unknown; only a retryable receipt can restore retryable work. Failed claim persistence prevents the send, and failed terminal cleanup cannot replay a partially accepted or unknown batch.
A complete OTLP response preserves rejected counts and warnings, even when HTTP 200 refused every record. Partial batches are retired without replaying admitted siblings. An unreadable success receipt is reported as unknown. Accepted counts describe intake acceptance, not unique stored rows or query visibility. An unavailable native bridge reports unknown with a reason.
Error and message capture return ErrorCaptureResult with nullable errorId, outcome and
reason. Outcomes are sampled, queued, rejected and unknown. Queued means local admission;
a sampled or rejected attempt can still have an ID, and that ID proves no emitted occurrence.
Use the trace result and then read the stored evidence before concluding delivery.
For product events, the cumulative Anectico.stats() counters and drop callbacks remain:
| What happened | Your events | flush |
stats()?.queued |
drop callback |
|---|---|---|---|---|
Permanently rejected — a bad request, or a key that is revoked, wrong, or missing the analytics:write scope |
discarded, never retried | failure | excludes refused positions | fires once |
| Temporarily unavailable — no network, rate limited, or a server error | still queued for the next flush | failure | includes them | silent |
Anectico.stats() reports recorded, delivered, dropped, and queued, and recorded always equals the
other three added together. delivered counts positions acknowledged queued in a complete version-one
capture ledger, not unique stored rows or immediate query visibility. queued includes in-flight events.
A mixed receipt retires queued and explicitly refused positions while preserving uncertain original
IDs, timestamps, identities and payloads ahead of unsent events. The durable spool retains those
uncertain events for recovery after restart. Malformed, oversized (over 64 KiB), incomplete or
contradictory receipts at any HTTP status retain the entire affected chunk. HTTP 200 alone cannot
claim delivery, and an untyped HTTP 4xx cannot authorize a drop. Without a server hint, consecutive uncertain attempts back off from one second, doubling up to
60 seconds and resetting after a fully settled receipt;
server Retry-After hints are capped at 60 seconds. The default HTTP attempt has a 15-second wait bound.
OTLP error and crash delivery uses its separate transport policy.
Session and identity changes remain effective in memory when local persistence fails. Logging out
rotates both identities even if storage cannot be cleared; a backward clock change starts a new
session. Property serialization stops at depth 32 and emits [MaxDepth] for deeper values. HTTP
redirects are rejected so event credentials are sent only to the configured endpoint.
Anectico.onDeliveryError(_:) fires once per discarded batch with the response HTTP status (including 200 for ledger refusals), how many events were
lost, and a stable lower-case reason. Ledger refusals use invalid_event, quota_exceeded, or
mixed_rejection when one chunk has multiple refusal causes. Typed request refusals use
bad_request, unauthorized, forbidden, or payload_too_large. Local drops report status 0: queue_overflow (the queue filled up and the oldest events were evicted) and
not_serializable (an event's properties could not be encoded). collection_disabled marks withdrawal
of pending or uncertain positions. A temporary failure alone does not fire it. The handler runs
outside the queue and bookkeeping locks, on the caller thread for local drops or the flush dispatcher
for delivery outcomes.
Capturing does not wait for network delivery: capture, identify, group, and screen serialize and persist the queued event, then return, so the delivery outcome reaches you through the three signals above and never through
their own return value.
Anectico.onDeliveryError { error in
print("lost \(error.dropped) events: \(error.reason.rawValue) (HTTP \(error.status))")
}
Anectico.flush { result in
print(result.productEvents.pending, result.traces.rejected, result.traces.unknown)
}
Withdraw analytics collection
Call Anectico.disableAnalytics() when analytics consent is withdrawn. It blocks subsequent
capture, screen, identify, group and analytics reset operations, stops the periodic analytics
timer, discards pending events, and records collection_disabled with status 0. SDK lifecycle
events also stop being admitted. Identity and group state are not changed by refused analytics calls.
The method performs synchronous local disk I/O but does not wait for network delivery, so a delivery
error callback may invoke it safely. Use flush(completion:) outside that callback to observe final
counters after an already-admitted request settles.
Already-admitted requests may finish. Validated queued or refused receipt positions keep their actual outcomes; uncertain positions and later unsent chunks are discarded without retry. A discarded uncertain event may already have reached the server. Withdrawal neither erases remote data nor stops the separate error, crash and OTLP producers.
The returned Bool confirms both the persisted withdrawal marker and deletion of the analytics
spool. If it is false, collection still stops in this configured process, but disk cleanup or marker
persistence failed: retry disableAnalytics() and do not assume restart safety until it returns
true. Calling it before configuration returns false. Repeated calls retry the disk operation
without reopening admissions or double-counting discarded events.
Ordinary configure(options) respects the durable marker after a restart. Only after obtaining new
consent should the application wait for shutdown completion, then call
try Anectico.configure(options, resumeAnalyticsCollection: true). Renewal removes the old spool even if the marker is absent
before clearing the marker; filesystem failure throws and leaves the SDK unconfigured. Never set
that argument automatically on every launch. React Native and Flutter expose corresponding awaited disableAnalytics and explicit renewed-consent configuration controls; see their SDK references.
AnecticoOptions
Only apiKey is required. Invalid endpoints, timers, capacities, frame limits, and non-finite or
out-of-range sampling values throw AnecticoConfigurationError rather than being clamped.
| Option | Type and default | Purpose |
|---|---|---|
apiKey |
String; required |
native_ingest key for OTLP. |
captureApiKey |
optional | Separate mobile_capture key for product events, identify and group. |
endpoint |
String; https://api.anectico.com |
Base URL for /v1/traces and /api/v1/capture. |
environment |
String; development |
deployment.environment resource value. |
release |
String?; service version when real |
Regression and symbol-artifact identifier. |
dist |
String; empty |
Build and artifact discriminator. |
serviceName |
String?; bundle ID |
service.name. |
serviceVersion |
String?; bundle marketing version |
service.version. |
flushAt |
Int; 20 |
Event-count flush threshold. |
flushIntervalMs |
Int; 5000 |
Foreground-only periodic event flush; zero disables the timer. |
maxStackTraceFrames |
Int; 50 |
Maximum frames per error. |
breadcrumbCapacity |
Int; 100 |
Ring-buffer capacity. |
maxQueueSize |
Int; 10000 |
Diagnostic-event queue cap; oldest entries drop at the cap. |
errorSampleRate |
Double; 1 |
Non-fatal error sampling in the inclusive range 0–1. |
enableCrashReporting |
Bool; true |
Install uncaught NSException and fatal-signal handlers. |
debug |
Bool; false |
Emit SDK diagnostics through Apple logging. |
Identity, accounts, and events
| Method and property | Parameters | Returns | Behavior |
|---|---|---|---|
Anectico.identify(distinctId, set:) |
stable ID; optional person properties | Void |
Links the anonymous ID to a known person and queues identify. |
Anectico.group(type:key:set:) |
account type and key; optional properties | Void |
Records membership and queues $groupidentify. |
Anectico.capture(event, properties:) |
event name; properties | Void |
Queues a diagnostic event with current person, session, and groups. |
Anectico.screen(name, properties:) |
screen name; properties | Void |
Queues $screen with $screen_name. |
Anectico.reset() |
— | Void |
Logout: creates a new anonymous ID, clears groups, rotates the session, and clears global error-user context and breadcrumbs. |
Anectico.distinctId |
property | String? |
Current known or anonymous ID, or nil before configuration. |
Anectico.sessionId |
property | String? |
Current durable session ID without extending its idle window. |
identify and group update local identity only after their control event passes queue admission.
An event that cannot be serialized or whose UTF-8 request envelope exceeds 1 MiB is dropped with
status 0 and reason not_serializable or payload_too_large. The previous person ID and account
memberships remain in use for subsequent telemetry. Valid controls are queued before events use
the new identity; the call does not wait for network delivery.
capture and screen inherit accounts set with group. An explicit
properties["$groups"] replaces the entire account map for that event; omitted
account types are not inherited. An empty dictionary suppresses account attribution
for that event. The override changes neither the cached accounts nor your property
dictionaries, so the next event without an override still inherits the cached accounts.
Errors, messages, users, and breadcrumbs
| Method | Parameters | Returns | Behavior |
|---|---|---|---|
Anectico.captureError(error, options:) |
Swift Error; optional CaptureOptions |
ErrorCaptureResult |
Captures a caught error and returns its ID, or "" before configuration. |
Anectico.captureError(exception, options:) |
NSException; optional options |
ErrorCaptureResult |
Captures an Objective-C exception without changing app behavior. |
Anectico.captureMessage(message, level:options:) |
message; level default info; options |
ErrorCaptureResult |
Captures a diagnostic message and returns its ID. |
Anectico.addBreadcrumb(category:message:level:data:) |
strings; level default info; data |
Void |
Adds trail context attached to a later error. |
Anectico.setUser(user) |
AnecticoUser |
Void |
Sets global error user context. Cross-signal identity still comes from identify. |
Anectico.clearUser() |
— | Void |
Clears global error user context. |
Anectico.captureRawError(...) |
type, message, frame JSON, fatal, mechanism, tags | ErrorCaptureResult |
Bridge API for React Native and Flutter pre-parsed stacks. Native Swift apps should use captureError. |
CaptureOptions fields are tags: [String:String], extra: [String:Any], optional user, optional
level, and optional fingerprint: [String]. Fingerprint components are comma-joined; do not put
commas inside components. AnecticoUser supports id, email, username, ipAddress, and segment.
Fatal native crashes are persisted and delivered on the next application launch. Keep the exact
archive dSYM and upload the bundle or its DWARF binary with
anectico symbols upload-dsym MyApp.dSYM. Anectico matches the project's dSYM slices by exact Mach-O
image UUID; release and distribution are not dSYM lookup keys.
For native symbolication, each frame carries its image UUID and load address. The complete loaded image set stays in the private crash sidecar; only a bounded table of frame-referenced images rides the recovered occurrence, so large simulator processes cannot exceed the ingest attribute cap.
Every iOS error occurrence automatically carries severity, handled state, an honest capture mechanism, release and distribution, SDK and platform, hardware model code or family, OS version, and app version and build. Caught Swift and explicitly captured Objective-C errors are handled and nonfatal; uncaught Objective-C exceptions and signals are unhandled and fatal. Signal crashes use the crash-time snapshot after relaunch.
These SDK-owned fields take precedence over colliding CaptureOptions.tags. The SDK records a model
code such as iPhone15,4, not a unique phone: it never collects IDFV, the user-assigned device name,
a serial number, or an installation ID.
The SDK-owned $app_opened lifecycle event carries the same privacy-safe release, app and build,
platform, OS, and device-family context. It is emitted once per real foreground transition, while
duplicate notifications in one active period are ignored. Backgrounding emits one
$app_backgrounded, pauses recurring flushes, and performs one explicit flush; foregrounding
recreates exactly one timer without rotating the session by itself. Anectico uses its versioned
lifecycle marker to count canonical-person release adoption and the adopted-person error-free rate;
host properties cannot override the marker context.
The reserved $release/$app_version string properties are stamped on every captured event
(not only $app_opened), sourced from the same release/serviceVersion configure() resolved.
Neither is invented: an app that never sets release and has no bundle version to fall back to
sends events with neither property. See
Reserved event properties.
Agent runs
Anectico.startAgentRun(_ agent: String, options: AgentRunOptions = AgentRunOptions()) -> AgentRun
starts a local run handle. It snapshots the start time, identity, and session.
let run = Anectico.startAgentRun(
"checkout-helper",
options: AgentRunOptions(agentId: "checkout", agentVersion: "1.4.0", conversationId: "chat-42")
)
// Perform the agent's work, then declare its actual outcome.
let accepted = run.end(.completed)
// On failure instead: run.end(.failed, terminalReason: "tool_failed")
agentId, agentVersion, and conversationId are optional strings mapping to
gen_ai.agent.id, gen_ai.agent.version, and gen_ai.conversation.id. The agent name maps to
gen_ai.agent.name. Configure and identify the customer before starting a run. A terminal record
is an invoke_agent span sent with ingest:write; it does not require an agent-read or agent-write
management scope. It is not automatic instrumentation of nested model and tool calls.
end(_ status: AgentRunTerminalStatus = .completed, terminalReason: String? = nil) -> Bool
accepts .completed, .failed, .timedOut, .cancelled, or .maxSteps. The multiword
wire values are timed_out and max_steps.
Use a stable, non-sensitive reason code, not an exception message, prompt, or customer value.
Reasons are trimmed, lowercased, and must match ^[a-z][a-z0-9_]{0,63}$. Omitted reasons default to
timeout for a timeout, user_cancelled for cancellation, and the wire status for other outcomes.
These are source-declared outcomes; Anectico does not infer success from the absence of an error.
An invalid reason returns false and leaves the handle open. The first accepted outcome wins;
later valid calls return true without emitting another span. Acceptance is a local handoff,
not confirmation of network delivery. The event-queue flush result does not certify this
separate span transport.
recordAgentRun(agent:options:status:terminalReason:startTimeUnixNano:) is the native bridge
entry point used by React Native and Flutter. Swift application code should use the typed
startAgentRun handle.
Backend propagation
| API | Parameters | Returns | Behavior |
|---|---|---|---|
Anectico.propagationHeaders() |
— | [String:String] |
Creates a fresh traceparent and identity baggage when configured. |
AnecticoPropagation.apply(to:&request) |
inout URLRequest |
Void |
Applies propagation while preserving unrelated baggage. |
AnecticoPropagation.newTraceparent() |
— | String |
Creates a sampled W3C traceparent. |
AnecticoPropagation.baggageWithDistinctId(id, existing:) |
ID; existing baggage | String |
Replaces the Anectico member and preserves unrelated baggage. |
AnecticoPropagation.percentEncode(value) |
string | String |
Encodes a W3C baggage value. |
Apply identity baggage only to application backends you trust. A backend must authenticate the mobile request rather than treating the baggage value itself as proof of identity.
Remote flags
Anectico.flags is FlagsClient?. It exists only with applicationEndpoint, canonical projectID
and flagDeliveryApiKey. Anectico.surveys independently requires surveyDeliveryApiKey.
surveyScreenPaths declares screen templates. applicationTransport optionally injects an
ApplicationTransport; it receives immutable ApplicationRequest values and a cancellation token.
The default transport bounds the complete attempt to ten seconds and the complete response to
1 MiB, refuses redirects and rejects truncated or invalid JSON. It shares no delivery credentials
or work queue with product and error collection.
These are source APIs awaiting iOS device and simulator qualification. Linux core tests establish protocol behavior, not an Apple platform build or stored customer evidence.
| Swift API | Result and meaning |
|---|---|
flags.reload(keys:) async throws |
FlagDecisionDocument: projectID, evaluatedAt, maxAgeSeconds, decisionContextID, decisions, complete, requestedKeyCount and expiresAt. |
flags.value(for:testCaseID:) |
FlagRead with status, optional bool or string value, payload, decision metadata, expiry and failure. Admits an allowed served exposure. |
flags.payload(for:testCaseID:) |
The same typed read, preserving absent payload and ApplicationJSON.null. |
flags.status(for:) |
The same state without exposure admission. |
flags.boolean(for:fallback:testCaseID:) |
FlagBooleanRead: value, reason, usedApplicationFallback, expiresAt and the single underlying read. Fallback is required; a string value yields type_mismatch. |
flags.setEvaluationBinding(_:) |
Replaces the current FlagEvaluationBinding independently of telemetry identity. |
flags.clearEvaluationBinding() |
Clears the explicit binding and retires its decisions and work. |
flags.evaluationSnapshot() |
Binding, evaluation generation, telemetry generation, configuration generation and whether the binding is explicit. |
flags.installBootstrap(_:) throws |
Installs only a matching original request snapshot and valid complete document, preserving the original expiry. |
Decision documents and flag reads carry expiresAtTimestamp, preserving the exact fractional expiry
text alongside expiresAt. Input context preserves optional inputBinding metadata. To construct
FlagBootstrap from original wire documents, pass projectID, the native request snapshot,
requestJSON, document and optional evaluationGeneration; a different binding or generation is
refused before installation.
Runtime application configuration keeps the existing telemetry recipient. Its first declared project stays bound to that recipient, including after clearing capabilities. Flags configured for another project can return decisions but admit no exposure through the original recipient. To collect exposure evidence in a different project, shut down and configure the SDK with that project's matching intake and delivery credentials.
FlagValue is .bool(Bool) or .string(String). ApplicationJSON supports null, bool, finite
number, string, array and object. FlagDecision retains state, value, payload, definitionVersion,
isFallback and inputContext. Evaluated definition versions remain their original UTC strings
with six fractional digits. Missing required-input metadata remains unknown. Invalid or missing
authoritative versions and fallback metadata cannot become evaluated decisions.
Decision states are evaluated, evaluation_error and unavailable_flag. FlagRead.status also
distinguishes unknown, expired, cancelled, refused, unconfigured, identity_unavailable and
transport_error. A safely returned server error fallback retains evaluation_error and isFallback;
it differs from your boolean convenience read's usedApplicationFallback. No unavailable value
is silently replaced with false. Optional testCaseID is application correlation, not proof.
Reload accepts 1–100 unique keys matching [A-Za-z0-9_-]{1,200}, ten groups and a 16 KiB JSON
targeting envelope. Properties are limited to depth 16, 250 entries per object or array, 512-byte
keys and 4,096 total values. The complete request is capped at 128 KiB. Responses must contain
exactly the requested keys, correct project and coverage, UTC evaluation time and a 30-second
max age. Concurrent identical reloads coalesce; a different request returns busy. Retry advice
sets a bounded retry floor. There is no background polling or persistent decision cache.
FlagBootstrap(projectID:request:keys:document:) takes the original FlagEvaluationSnapshot,
requested keys and complete JSON decision bytes. Bootstrap rejects a changed project, exact
identity, properties, groups, key set or generation. Installation never restarts its lifetime.
When product collection is unavailable, supply a current explicit binding or evaluation sends nothing and returns identity_unavailable. An explicit binding emits no telemetry identity event and does not change the telemetry session. Exposures require consent and the matching admitted telemetry subject. Deduplication includes identity, key, typed value and exact definition version. Uncertain product retries retain original IDs, timestamps, sessions, properties and batch bytes, including across restart. A changed capture endpoint, key or explicit project discards pending product events with a warning; flush successfully before changing them. Recipient pins contain no raw credentials. Consent withdrawal remains in force across those changes.
Headless surveys
| Swift API | Result and meaning |
|---|---|
surveys.setScreenMapping(_:) throws |
Declares static screen names and slash-prefixed templates; retires old screen work. |
surveys.refresh(screen:) async throws |
At most three SurveyOffer values; explicit refreshes and screen changes coalesce with a five-second minimum gap. |
offer.survey, id, revision, questions, isClosed |
Immutable offered definition and current handle availability. Revision is Int64. |
offer.shown(id:) async throws |
Validated SurveyReceipt; call only after actual display. |
offer.submit(answers:responseID:) async throws |
Validated receipt for copied SurveyAnswer values. Rating zero is present. |
offer.dismiss(id:) async throws |
Validated dismissal receipt. |
SurveyReceipt contains the original at timestamp string and replayed boolean. Malformed
success documents are uncertain, never submitted. ApplicationFailure retains kind, code,
status, retryable and retryAfterMs. SURVEY_IDENTITY_COVERAGE_UNSUPPORTED is distinct from a
valid empty result. Permanent refusal closes the handle; retryable failure retains only its
immutable in-memory operation. Repeating the operation resends its first body, including its
original session, answers and ID, regardless of new arguments.
SurveyAnswer(questionID:rating:optionIDs:text:) uses the field for that question's type. There
are 1–10 questions, 2–12 distinct choice options, stable lowercase IDs of at most 32 characters,
prompts up to 500 characters and labels up to 200. Rating bounds start at 0 or 1 and end at no more
than 10. Open text is normalized and accepts 1–2,000 characters. Unknown questions, duplicate
answers, unknown or duplicate options, missing required answers and fractional ratings are refused.
Delivery IDs are 1–128 printable ASCII bytes without spaces. Environment and source session are
at most 200 UTF-8 bytes; session identifiers follow capture's identifier rules.
Surveys require product consent and available product identity. Withdrawal, admitted identity or group changes, reset, shutdown and screen replacement close incompatible handles and clear copied answers. No open-text spool or client survey-outcome event is created. A receipt is delivery acknowledgement; verify stored answers and the joined customer timeline separately. Multiple choice rows are not response totals. The flag API adds no separate public exposure receipt.
Verify this source and deployment
Follow mobile evidence verification for exact flag versions, originating message and source IDs, aliases and per-device sessions. Decisions, exposure admission, survey receipts and stored evidence are separate checks. The native flag API has no public per-exposure receipt; original-attempt attribution remains unqualified. An ID discovered only after storage and aggregate flush counts cannot replace it.
The client preserves optional survey source sessions and recognizes
SURVEY_IDENTITY_COVERAGE_UNSUPPORTED; the server propagates source sessions and refuses
incomplete aliases. Independently verify stored correlation on your deployment. Earlier web
or REST runs do not qualify the changed client-delivery authority. Treat an empty eligible list, transport failure, typed refusal
and permanent delivery rejection separately. Retention, content policy, erasure, export and local
withdrawal still apply; withdrawal does not erase data already stored. Read existing person and
result proof links where supplied; flag evaluation has no proof page.
Use explicit private ingestion and application HTTPS hosts and the approved private network path. Do not expose the API publicly or put operator credentials into a build. Pin source revisions; source coordinates, package artifacts and core test results are not proof of package publication, device installation or stored cross-device qualification. React Native and Flutter have typed native delivery bridges; linked operating-system qualification remains unverified.
Distinct decision and answer examples
Configure independent definitions before expecting enabled true, disabled false and
checkout_variant equal to the string treatment for the current ID and context. They are
example keys, not built-in definitions. A missing key is unavailable; a deliberately failed
dependency has an evaluation-error default and no authoritative version. Explicit fallback
use must stay observable rather than being mistaken for evaluated false.
let document = try await flags.reload(keys: ["enabled", "disabled", "checkout_variant"])
let enabled = flags.boolean(for: "enabled", fallback: false)
let disabled = flags.boolean(for: "disabled", fallback: true)
let variant = flags.value(for: "checkout_variant", testCaseID: "mobile-variant")
// Inspect typed reasons, fallback use and decision metadata before choosing behavior.
For an actual offered survey whose questions are plan (single choice with option pro),
features (multiple choice with options speed and support), score (rating 0–10) and
comment (open text), these are the four answer shapes. Read the offered questions and use
what the visitor actually selected; do not submit these constants automatically:
let answers = [
SurveyAnswer(questionID: "plan", optionIDs: ["pro"]),
SurveyAnswer(questionID: "features", optionIDs: ["speed", "support"]),
SurveyAnswer(questionID: "score", rating: 0),
SurveyAnswer(questionID: "comment", text: "Checkout was clear")
]
// In the actual display callback: _ = try await offer.shown()
// In the explicit user-submit callback:
let receipt = try await offer.submit(answers: answers)
// Alternative user-dismiss callback, not after submitting: try await offer.dismiss()
A rating of zero is present. This one response generates five answer event rows: one single choice, two selected options, one rating and one text. It remains one accepted response. The application owns rendering and callbacks. Do not record shown merely because refresh returned an offer, or dismiss a survey already submitted. Retry the same uncertain operation on the same handle with its original identity, optional session, revision, answers and ID.
Native replay controls
AnecticoOptions.nativeReplay defaults to absent. NativeReplayOptions.enabled defaults to false.
An enabled controller needs explicit replay HTTPS origin, project ID, separately issued replay-upload
key, the existing mobile capture key and a current native policy with native store permission.
Endpoint or key syntax is not proof of their server-side authority. See setup.
| API | Behavior |
|---|---|
Anectico.replay |
Optional owned controller, independent of flags and analytics consent |
bindReplayOwner(_:) |
Explicit exact UTF-8 ID, 1–200 bytes; never inferred from a previous identity |
grantReplayConsent() |
Explicit local replay consent; not renewed by configuration |
withdrawReplayConsent() |
Cancels work and purges unsent local content; returns local purge success |
start() |
disabled, recording, paused or refused, plus reason, format and version and capture revision |
diagnostics() |
Pending chunks, envelope bytes, bounded loss counts and accepted-with-retention-unknown count |
selectReplaySource(_:adapter:) |
One hierarchy or external source per recording; changing closes and rotates |
acquireReplayCaptureContext() |
One opaque current token, at most five seconds and never beyond policy expiry |
submitReplayStructure(token:sourceSequence:events:nodeKeys:buffered:) |
Synchronous atomic admission; buffered defaults to false. Buffered submission can also return unchanged; native assigns ordinals |
endInputEpisode(token:frameID:nodeID:) |
External focus-loss hook, bound to the current token and input target; records no value or event |
reconfigure(_:) |
Retires pending content and old contexts before new endpoint, key, project or privacy configuration |
flush() |
Attempts one original chunk; a matching receipt returns acceptedRetentionUnknown |
shutdown() |
Stops extraction, attempts one bounded final upload, then purges unsent content |
External candidates are sanitized NR1 JSON without ordinals, at most 16 events, one frame and
65,536 bytes. Sequence starts at one and advances only for an admitted batch. Native read-only context facts
recordingID, elapsedMs and nextSourceSequence let an adapter reset its source namespace after
rotation and continue it after token renewal; these facts confer no upload authority. Renewal never resets
sequence or frame references. Optional node declarations match privacy rules but are not recorded.
A stale token cannot authorize a new owner or source. Selecting an external source disables UIKit
traversal; it does not qualify a framework adapter.
| UIKit source classes | Observed structure |
|---|---|
| UIView and UIStackView | Bounded containers |
| UILabel | Masked text box, no text |
| UIButton | Button box and explicitly declared eligible action label only |
| UITextField and UITextView | Masked input placeholder; no value, length or per-keystroke count |
| UISwitch | Masked toggle, no checked state |
| UIImageView | Masked box, no asset or URL |
| UIScrollView | Masked scroll box with explicit bounded offsets |
| Custom subclasses, SwiftUI hosts, embedded web and custom drawing | Unsupported placeholder and gap |
The source uses exact supported classes and explicit app handlers. Frame and action hooks observe after the native consent gate and encode on the replay worker; read their asynchronous completion. Masks dominate labels and propagate to descendants. A changed declaration immediately retires cached targets and pending content. Blocks and unsupported regions stop child inspection. Input-change hooks coalesce an editing episode until focus loss, screen change or pause. No accessibility text, pixels, executable content, console or network bodies enter this format. Optional labels are bounded, redacted application declarations and remain untrusted evidence.
Hard safety ceilings include two frames and 20 events per rolling second, 256 nodes per frame and 32 levels, 262,144 bytes per chunk, eight local chunks and 2,097,152 envelope bytes, 60-second local age and ten-minute recordings. Local and remote ceilings only narrow these maxima. They are not measured device rates or allowances. A full frame drops as a whole and declares loss; incomplete hierarchies are never presented as complete screens. Disk faults refuse capture.
Retries preserve original bytes, recipient, owner, session, format and index. All receipt bindings, including the SHA-256 of the exact submitted event array, must match before a local item is released. Malformed or same-sized different-body receipts remain uncertain. Native policy is refreshed at most 15 seconds apart and expires within 60 seconds; a changed projection retires old content. Restarts require fresh explicit owner, consent and policy before any compatible original retry. Withdrawal, reset, owner changes, policy retirement and ordinary shutdown purge unsent content. Stop is not server erasure, and an accepted upload does not establish retained content or completed playback.
Replay capture and durability
A UIKit worker capture can return unchanged when the observed scene matches the latest admitted
frame. It adds no frame, event or upload and leaves action targets on that exact frame. Screen or
viewport changes, a coverage gap, pause or a change of owner, consent or policy requires a fresh frame.
Unsupported regions keep their gap markers.
Buffered capture stores sanitized events in one bounded open chunk. It becomes durable at eight frames, 64 events or two seconds after its first event, and when you flush, background or shut down. Withdrawal and every other privacy purge discard open events too. A crash can lose that open interval; recovery reports a gap with unknown counts. Synchronous forced snapshots keep their immediate durability. An admitted result confirms local acceptance; an upload acknowledgement still does not establish retention or playback availability.