Skip to content

Quickstart ​

Five steps, in every language we ship an SDK for: install, initialise, track an event, identify a user, verify it arrived. Pick your tab and stay in it — the same five steps run top to bottom.

Two placeholders appear throughout. Substitute your own:

PlaceholderWhat to put there
pk_live_xxxYour API key. pk_ in a browser or mobile app, sk_ on a server — see Authentication.
https://collect.tangentlens.com/collectorYour collector base URL.

Nothing is published to a public registry yet

No SDK is on npm, PyPI, Maven Central, the Go proxy, the Swift Package Index or CocoaPods today. Every install below is the path that works right now — a checkout, a path dependency or the tag served by the collector. The registry commands are on each SDK page, labelled as not yet working.

1. Install ​

html
<!-- In <head>. The collector serves the tag, so the build always matches the collector you send to.
     Async: your page never waits for Tangent, even when it is down. -->
<script>
  // Loads Tangent in the background: your page never waits for it, and calls made before it
  // arrives are queued. If Tangent is down, your page is unaffected.
  (function (w, d, url) {
    if (w.selica && !w.selica.q) return;
    var METHODS = ['init','identify','setUserProperties','setDeviceId','setGroup','clearGroups',
      'addEntity','clearEntities','track','revenue','add','remove','page','flush','flushWithBeacon',
      'optOut','optIn','consent','reset','feedback','showSurvey','startReplay','stopReplay'];
    var stub = w.selica || {}; stub.q = stub.q || [];
    for (var i = 0; i < METHODS.length; i++) (function (name) {
      stub[name] = function () { stub.q.push([name, [].slice.call(arguments)]); return stub; };
    })(METHODS[i]);
    w.selica = stub;
    var s = d.createElement('script'); s.async = true; s.src = url;
    var first = d.getElementsByTagName('script')[0];
    if (first && first.parentNode) first.parentNode.insertBefore(s, first); else d.head.appendChild(s);
  })(window, document, 'https://collect.tangentlens.com/collector/selica-analytics.js');
</script>
html
<!-- index.html (public/index.html with Create React App), in <head>. The stub makes window.selica
     exist at once, so components can call it before the script arrives; nothing waits for it. -->
<script>
  // Loads Tangent in the background: your page never waits for it, and calls made before it
  // arrives are queued. If Tangent is down, your page is unaffected.
  (function (w, d, url) {
    if (w.selica && !w.selica.q) return;
    var METHODS = ['init','identify','setUserProperties','setDeviceId','setGroup','clearGroups',
      'addEntity','clearEntities','track','revenue','add','remove','page','flush','flushWithBeacon',
      'optOut','optIn','consent','reset','feedback','showSurvey','startReplay','stopReplay'];
    var stub = w.selica || {}; stub.q = stub.q || [];
    for (var i = 0; i < METHODS.length; i++) (function (name) {
      stub[name] = function () { stub.q.push([name, [].slice.call(arguments)]); return stub; };
    })(METHODS[i]);
    w.selica = stub;
    var s = d.createElement('script'); s.async = true; s.src = url;
    var first = d.getElementsByTagName('script')[0];
    if (first && first.parentNode) first.parentNode.insertBefore(s, first); else d.head.appendChild(s);
  })(window, document, 'https://collect.tangentlens.com/collector/selica-analytics.js');
</script>
json
// package.json — depend on the checked-out SDK by path
{ "dependencies": { "@tangent/analytics-node": "file:../analytics/sdk/node" } }
bash
# from a checkout of the repository
pip install ./sdk/python
bash
git clone https://github.com/selicatech/tangent
cd tangent/sdk/java
mvn -DskipTests install
# -> com.selicatech.analytics:selica-analytics-sdk:1.1.0 in ~/.m2/repository
go
// go.mod — a replace directive against a local checkout
module github.com/northwind/api

go 1.23

require github.com/selicatech/analytics-go/v3 v0.0.0

replace github.com/selicatech/analytics-go/v3 => ../tangent/sdk/go
swift
// Package.swift — a local path dependency.
// For a path dependency the package identity is the DIRECTORY NAME ("swift"), not
// the name the package declares.
dependencies: [
    .package(path: "../analytics/sdk/swift"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "SelicaAnalytics", package: "swift"),
    ]),
]
kotlin
// settings.gradle.kts — a composite build against the SDK source
includeBuild("../analytics/sdk/android")

// app/build.gradle.kts
dependencies {
    implementation("com.selicatech:selica-analytics-android:2.3.0")
}

2. Initialise ​

html
<script>
  selica.init({
    apiKey: 'pk_live_xxx',
    endpoint: 'https://collect.tangentlens.com/collector',
  });
</script>
js
// src/analytics.js — initialise at MODULE scope, not in an effect. A module is
// evaluated once per bundle, which survives StrictMode, Fast Refresh and remounts.
const client = window.selica;

let started = false;

export function initAnalytics() {
  if (started) return client;
  started = true;
  client.init({
    apiKey: import.meta.env.VITE_TANGENT_KEY || 'pk_live_xxx',
    endpoint: 'https://collect.tangentlens.com/collector',
  });
  return client;
}
js
const { createClient } = require('@tangent/analytics-node');

const selica = createClient({
  apiKey: 'sk_live_xxx',
  endpoint: 'https://collect.tangentlens.com/collector',
});
python
from selica_analytics import Client

selica = Client(
    api_key="pk_live_xxx",
    server_url="https://collect.tangentlens.com/collector",
)
java
import com.selicatech.analytics.sdk.*;
import java.util.Map;

SelicaClient selica = SelicaClient.builder("pk_live_xxx")
        .serverUrl("https://collect.tangentlens.com/collector")
        .build();
go
client, err := selica.New(selica.Config{
    APIKey:    "pk_live_xxx",
    ServerURL: "https://collect.tangentlens.com/collector",
})
if err != nil {
    log.Fatal(err)
}
defer client.Close() // flushes what is buffered
swift
import SelicaAnalytics

var config = SelicaConfig(apiKey: "pk_live_xxx")
config.serverUrl = "https://collect.tangentlens.com/collector"
let selica = try SelicaClient(config: config)
kotlin
val selica = SelicaClient(
    SelicaConfig(
        apiKey = "pk_live_xxx",
        endpoint = "https://collect.tangentlens.com/collector",
        storage = SharedPrefsStorage(this), // the default does not persist
    ),
)

3. Track an event ​

An event is a name plus properties. Name it after something that happened, in object_verb form, and keep the set of names small enough to reason about.

js
selica.track('report_exported', { format: 'csv', rows: 1240 });
jsx
import { useAnalytics } from './analytics';

function ExportButton() {
  const selica = useAnalytics();
  return (
    <button onClick={() => selica.track('report_exported', { format: 'csv', rows: 1240 })}>
      Export
    </button>
  );
}
js
// On a server nothing is sticky: name the subject on every call.
selica.track('report_exported', { format: 'csv', rows: 1240 }, { userId: 'user-1042' });
python
selica.track(
    "report_exported",
    user_id="user-1042",
    properties={"format": "csv", "rows": 1240},
)
java
selica.track("report_exported", "user-1042", Map.of("format", "csv", "rows", 1240));
go
client.TrackEvent("report_exported", map[string]any{"format": "csv", "rows": 1240})
swift
selica.track("report_exported", properties: ["format": "csv", "rows": 1240])
kotlin
selica.track("report_exported", mapOf("format" to "csv", "rows" to 1240))

4. Identify a user ​

Call this the moment you know who the person is. The anonymous deviceId is kept, so the history from before sign-in joins to the person rather than being orphaned.

On a server, identity is per call, not per client — see the note in each server SDK page before you reach for identify() in a request handler.

js
selica.identify('user-1042', { plan: 'pro', signup_source: 'referral' });
jsx
useEffect(() => {
  if (user) selica.identify(user.id, { plan: user.plan });
}, [user]);
js
// Process-wide. Correct for a CLI or a worker; wrong inside a request handler,
// where concurrent requests would overwrite each other. Pass userId per event instead.
selica.identify('user-1042', { plan: 'pro', country: 'IE' });
python
selica.identify("user-1042", {"plan": "pro", "company": "Northwind"})
java
selica.identify("user-1042", Map.of("plan", "pro", "company", "Northwind"));
go
client.Identify("user-1042", map[string]any{"plan": "pro", "company": "Northwind"})
swift
selica.identify("user-1042", traits: ["plan": "pro"])
kotlin
selica.identify("user-1042", mapOf("plan" to "pro"))

5. Verify it arrived ​

Every SDK buffers and flushes on an interval — five seconds by default on the mobile SDKs. Force the flush so you are not waiting on a timer, then look for the event.

js
selica.flush();
// Then: DevTools → Network → the POST to /v1/events. A 200 with
// `"failed": []` means every event in the batch was accepted.
js
selica.flush();
// DevTools → Network → POST /v1/events
js
await selica.flush();
await selica.shutdown(); // drains the queue before the process exits
python
selica.flush()
java
selica.flush();
selica.close(); // SelicaClient is AutoCloseable — try-with-resources works too
go
client.Close() // flushes what is buffered
swift
selica.flush()
kotlin
selica.flush()

Then confirm from the outside, independently of your code. Check the key first — this answers without a session, so it still works when sign-in is the broken thing:

bash
curl "https://collect.tangentlens.com/collector/v1/key/validate" \
  -H "X-API-Key: pk_live_xxx"

A 200 returns the key's appId, environment and keyType. A 401 means the key is wrong, revoked, or belongs to another environment.

Then open Events in Tangent and look for report_exported. If the POST returned 200 but nothing shows, check the Events → Failed tab: an event accepted at the edge but unstorable later is quarantined with its stage and reason for 30 days, never silently dropped.

Where to go next ​

  • Your SDK's own page — the full configuration table, plugins, and its troubleshooting matrix. Start at Browser or the sidebar.
  • Authentication — key kinds, origin allow-lists, rotation, revocation.
  • HTTP ingest API — the wire format underneath all of this. There is nothing an SDK sends that you cannot send with curl.
  • SDK support matrix — what each SDK can and cannot do.

Tangent — product analytics.