# SDK reference (/docs/reference/sdk)

Setup is covered in [Install the SDK](/docs/getting-started/install). Every browser method is safe to call before `init`, during server-side rendering, or after opting out: it simply does nothing.

## Browser [#browser]

### init options [#init-options]

`datahop.init(token, options?)`

<TypeTable
  type={{
    host: { type: 'string', default: "'https://app.datahop.dev'", description: 'Where to send data. Use your custom domain if you set one up.' },
    autocapturePageviews: { type: 'boolean', default: 'true', description: 'Capture pageviews on load and on navigation, and page leaves when the tab is closed.' },
    persistence: { type: "'localStorage' | 'cookie' | 'memory'", default: "'localStorage'", description: 'Where the anonymous id and session are kept. memory stores nothing, so every page load is a new visitor.' },
    flushAt: { type: 'number', default: '20', description: 'Send as soon as this many events are queued.' },
    flushInterval: { type: 'number', default: '2000', description: 'Longest time (ms) an event waits before it is sent.' },
    debug: { type: 'boolean', default: 'false', description: 'Log every event and failed request to the console.' },
  }}
/>

### Methods [#methods]

| Method | What it does |
| --- | --- |
| `capture(event, properties?)` | Send an event. |
| `register(properties)` · `unregister(key)` | Add a property to every later event · remove it. |
| `identify(id, $set?, $set_once?)` | Link the visitor to your user id. See [Identify users](/docs/getting-started/identify). |
| `setPersonProperties($set, $set_once?)` | Update the current person's properties. |
| `reset()` | Start over as a new anonymous visitor. Call it on logout. |
| `getDistinctId()` · `getSessionId()` | The current ids. |
| `group(type, key, properties?)` | Attach this user and every later event to a group. |
| `resetGroups()` · `getGroups()` | Stop attaching groups · the current groups. |
| `getFeatureFlag(key, { sendEvent? })` | A flag's value. Records an exposure unless `sendEvent: false`. |
| `isFeatureEnabled(key, { sendEvent? })` | Whether a flag is on (or any variant). |
| `getFeatureFlagPayload(key)` | The payload for the user's value. |
| `getAllFlags()` | Every flag value. |
| `onFeatureFlags(callback)` | Runs whenever flags load. Returns an unsubscribe function. |
| `reloadFeatureFlags()` | Fetch flags again. |
| `overrideFeatureFlags(flags \| false)` | Force values locally for testing. `false` clears them. |
| `feedback({ message, rating?, category?, email?, url?, properties?, anonymous? })` | Send feedback. Resolves with its id. See [Feedback](/docs/feedback). |
| `optOut()` · `optIn()` · `hasOptedOut()` | Stop or resume capturing for this browser. |
| `flush()` | Send everything queued now. |
| `shutdown()` | Flush and stop this instance (tests, hot reload). |

For a second project on the same page, create another instance: `new Datahop().init('dh_other_token', { … })`.

## Node / Bun [#node--bun]

`new Datahop({ token, host, … })`

<TypeTable
  type={{
    token: { type: 'string', required: true, description: 'Your project token.' },
    host: { type: 'string', default: "'https://app.datahop.dev'", description: 'Where to send data.' },
    flushAt: { type: 'number', default: '50', description: 'Send as soon as this many events are queued.' },
    flushInterval: { type: 'number', default: '5000', description: 'Longest time (ms) an event waits before it is sent.' },
    maxInFlight: { type: 'number', default: '1', description: 'Requests sent at once. Raise it for big imports.' },
    onError: { type: '(error, events) => void', description: 'Called when events are dropped or a flag request fails.' },
  }}
/>

| Method | What it does |
| --- | --- |
| `capture({ distinctId, event, properties?, groups?, $set?, $set_once?, timestamp?, uuid? })` | Send an event. `timestamp` for imports, `uuid` to make re-sends safe. |
| `identify({ distinctId, anonDistinctId?, properties?, $set_once? })` | Set person properties, and optionally link a browser's anonymous id. |
| `setPersonProperties({ distinctId, $set, $set_once? })` | Update person properties. |
| `groupIdentify({ groupType, groupKey, properties? })` | Create or update a group. |
| `getFeatureFlag({ distinctId, key, groups?, personProperties?, groupProperties?, sendEvent? })` | A flag's value. Records an exposure unless `sendEvent: false`. |
| `isFeatureEnabled(…)` · `getFeatureFlagPayload(…)` | Same inputs as `getFeatureFlag`. |
| `getAllFlags({ distinctId, groups?, … })` | Every flag value. |
| `feedback({ message, distinctId?, rating?, category?, groups?, … })` | Send feedback right away. Resolves with its id. |
| `flush()` · `shutdown()` | Send what's queued · send and stop. Await one before the process exits. |

`pending`, `sent` and `dropped` count events, which is handy in import scripts.

## HTTP endpoints [#http-endpoints]

All three use your public project token and work from any website.

### POST /i/batch [#post-ibatch]

Send up to 500 events.

<TypeTable
  type={{
    token: { type: 'string', required: true, description: 'Project token.' },
    batch: { type: 'Event[]', required: true, description: '1–500 events.' },
    sent_at: { type: 'ISO datetime', description: 'When you sent the request, by the device clock. Datahop uses it to fix wrong device clocks. Leave it out for imports.' },
  }}
/>

Each event:

<TypeTable
  type={{
    uuid: { type: 'uuid', required: true, description: 'Unique per event. Reuse it on retries: the same uuid is stored once.' },
    event: { type: 'string', required: true, description: 'Event name, up to 200 characters.' },
    distinct_id: { type: 'string', required: true, description: 'Who did it, up to 200 characters.' },
    timestamp: { type: 'ISO datetime', required: true, description: 'When it happened, with a time zone (Z or ±hh:mm).' },
    properties: { type: 'object', description: 'Any JSON. Use $groups for groups, e.g. { "$groups": { "company": "acme" } }.' },
    $set: { type: 'object', description: 'Person properties to overwrite.' },
    $set_once: { type: 'object', description: 'Person properties to set only if missing.' },
  }}
/>

Returns `{ "ok": true, "accepted": n }`. A `400` means the payload is invalid and the whole batch was rejected. A `401` means the token is wrong. Retry `429`, `5xx` and network errors. The body can be up to 5 MB.

Special events work as they do in the SDKs:

- `$identify` with `properties.$anon_distinct_id` links an anonymous id to `distinct_id`.
- `$groupidentify` with `$group_type`, `$group_key` and `$group_set` creates or updates a group.
- `$feature_flag_called` with `$feature_flag` and `$feature_flag_response` records a flag exposure.

### POST /i/flags [#post-iflags]

```json
{ "token": "dh_…", "distinct_id": "user_123", "groups": { "company": "acme" }, "person_properties": { "plan": "pro" } }
```

Returns `{ "flags": { "new-checkout": "test", "dark-mode": true }, "payloads": {} }` with every flag that's on. `groups` is needed for flags that match by company. `person_properties` and `group_properties` add to the stored properties for this check only. This doesn't record exposures.

### POST /i/feedback [#post-ifeedback]

```json
{ "token": "dh_…", "message": "Love the new dashboard", "distinct_id": "user_123", "rating": 5, "category": "praise" }
```

Returns `{ "ok": true, "id": "…" }`. All fields are on the [Feedback](/docs/feedback#send-feedback) page.
