# iOS SDK API

> Functions, options, parameters, return values, and propagation helpers for the Anectico Swift SDK.

Canonical page: https://anectico.com/docs/reference/ios-sdk/


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/error network work runs off the calling thread.

Use a project key with `ingest:write` and `analytics:write`. The native SDK does not record replay.

## Configure the SDK

```swift
import Anectico

try Anectico.configure(
    AnecticoOptions(
        apiKey: "an_...",
        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.flush(completion:)` | callback taking `Bool` | `Void` | Starts an asynchronous diagnostic-event flush and hands the completion whether every batch it attempted was accepted. See [Knowing whether your events arrived](#knowing-whether-your-events-arrived). |
| `Anectico.shutdown(completion:)` | callback taking `Bool` | `Void` | Stops timers/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/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

`flush` answers one question: **was everything the SDK had queued accepted?** It reports success only
when every batch it attempted was accepted, and finding an empty queue is a success — nothing was
left unaccepted. Errors and crash reports travel their own pipeline and are not covered by this
answer.

A failure has two shapes, and `Anectico.stats()` separates them:

| 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.

```swift
Anectico.onDeliveryError { error in
    print("lost \(error.dropped) events: \(error.reason.rawValue) (HTTP \(error.status))")
}

Anectico.flush { accepted in
    if !accepted, let stats = Anectico.stats() {
        // stats.queued > 0 means a retry is pending; otherwise the events are gone.
    }
}
```

## 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/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/default | Purpose |
| --- | --- | --- |
| `apiKey` | `String`; required | Key sent to error and event endpoints. |
| `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/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/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/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/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/anonymous ID, or nil before configuration. |
| `Anectico.sessionId` | property | `String?` | Current durable session ID without extending its idle window. |

## Errors, messages, users, and breadcrumbs

| Method | Parameters | Returns | Behavior |
| --- | --- | --- | --- |
| `Anectico.captureError(error, options:)` | Swift `Error`; optional `CaptureOptions` | `String` | Captures a caught error and returns its ID, or `""` before configuration. |
| `Anectico.captureError(exception, options:)` | `NSException`; optional options | `String` | Captures an Objective-C exception without changing app behavior. |
| `Anectico.captureMessage(message, level:options:)` | message; level default `info`; options | `String` | 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 | `String` | Bridge API for React Native/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 organization-scoped 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/distribution, SDK/platform, hardware model code/family, OS version, and app
version/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/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](/docs/investigate/event-schema#reserved-properties).

## Agent runs

`Anectico.startAgentRun(_ agent: String, options: AgentRunOptions = AgentRunOptions()) -> AgentRun`
starts a local run handle. It snapshots the start time, identity, and session.

```swift
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/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.

- [Install and use iOS](/docs/instrument/ios)
- [Mobile SDK behavior](/docs/instrument/mobile)
- [Replay and symbol troubleshooting](/docs/help/replay-and-symbols)
