# HTTP API

## Quick reference

- **Base URL**

  api.altertable.ai

- **Authentication**

  [Public API key](#authentication)

- **Recommended client**

  [JavaScript SDK](/docs/ingest-data/product-analytics.md#sdk-quick-start)

* [Track events](#post-track)
* [Identify users](#post-identify)
* [Merge identities](#post-alias)

The Product Analytics API provides direct HTTP access to track events and identify users. For most use cases, we recommend using the [JavaScript or React SDKs](/docs/ingest-data/product-analytics.md#sdk-quick-start), which provide a simpler interface and handle session management automatically.

Successful responses return the same JSON shape: `ok`, `task_id`, and `error_code`.

## API endpoint

| Hostname            | Port        |
| ------------------- | ----------- |
| `api.altertable.ai` | 443 (HTTPS) |

## Authentication

All API requests require a public Product Analytics API key.

| Method                 | Value                                |
| ---------------------- | ------------------------------------ |
| `X-API-Key` header     | `X-API-Key: YOUR_API_KEY`            |
| `Authorization` header | `Authorization: Bearer YOUR_API_KEY` |
| Query parameter        | `?apiKey=YOUR_API_KEY`               |

See the [Authentication guide](/docs/ingest-data/product-analytics/authentication.md) for details on obtaining and managing API keys.

## POST `/track`

Track one event or a batch of events.

**Authentication:** Public Product Analytics API key\
**Permission:** Write events

| Parameter      | Type                       | Location | Required | Description                                                                 |
| -------------- | -------------------------- | -------- | -------- | --------------------------------------------------------------------------- |
| `sync`         | boolean                    | query    | No       | Wait for ingestion to finish before returning when true.                    |
| `environment`  | string                     | body     | Yes      | Environment slug such as production or staging.                             |
| `event`        | string                     | body     | Yes      | Event name.                                                                 |
| `properties`   | object                     | body     | Yes      | Custom key-value properties. Send an empty object when no properties apply. |
| `distinct_id`  | string                     | body     | No       | User or visitor identifier.                                                 |
| `anonymous_id` | string                     | body     | No       | Anonymous identifier used before the user is known.                         |
| `device_id`    | string                     | body     | No       | Device identifier.                                                          |
| `session_id`   | string                     | body     | No       | Session identifier.                                                         |
| `timestamp`    | integer \| ISO 8601 string | body     | No       | Event time as Unix milliseconds or an ISO 8601 timestamp.                   |

The response fields describe whether the API accepted the event:

- `ok`: whether the request was accepted
- `task_id`: task identifier you can use to track asynchronous processing
- `error_code`: endpoint-specific error code when the request was not successful

### Error codes

- `environment-not-found`
- `invalid-headers`
- `unknown-built-in-property`
- `event-name-too-long`
- `property-name-too-long`

Event names and property names can contain up to 400 characters. Requests with longer names are rejected before ingestion.

### Request

```curl
curl -X POST https://api.altertable.ai/track \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "environment": "production",
  "event": "Purchase Completed",
  "properties": {"amount": 99.99, "currency": "USD"},
  "distinct_id": "u_01jza857w4f23s1hf2s61befmw"
}'
```

```javascript
await fetch('https://api.altertable.ai/track', {
method: 'POST',
headers: {
  'Content-Type': 'application/json',
  'X-API-Key': process.env.ALTERTABLE_API_KEY,
},
body: JSON.stringify({
  environment: 'production',
  event: 'Purchase Completed',
  properties: { amount: 99.99, currency: 'USD' },
  distinct_id: 'u_01jza857w4f23s1hf2s61befmw',
}),
});
```

### Response (200 OK)

```json
{"ok":true,"task_id":"019d68d4-c4cb-7e40-ad50-fd4b68c2045e","error_code":null}
```

## POST `/identify`

Create or update one identity or a batch of identities.

**Authentication:** Public Product Analytics API key\
**Permission:** Write identities

| Parameter      | Type                       | Location | Required | Description                                              |
| -------------- | -------------------------- | -------- | -------- | -------------------------------------------------------- |
| `sync`         | boolean                    | query    | No       | Wait for ingestion to finish before returning when true. |
| `environment`  | string                     | body     | Yes      | Environment slug such as production or staging.          |
| `distinct_id`  | string                     | body     | Yes      | The user's unique identifier.                            |
| `anonymous_id` | string                     | body     | No       | Previous visitor ID used to link anonymous events.       |
| `traits`       | object                     | body     | No       | User traits to shallow-merge into the existing identity. |
| `timestamp`    | integer \| ISO 8601 string | body     | No       | Timestamp used for last-write-wins ordering.             |

The response fields describe whether the API accepted the identity:

- `ok`: whether the request was accepted
- `task_id`: task identifier you can use to track asynchronous processing
- `error_code`: endpoint-specific error code when the request was not successful

### Error codes

- `environment-not-found`
- `trait-name-too-long`

Trait names can contain up to 400 characters. Requests with longer trait names are rejected before ingestion.

### Request

```curl
curl -X POST https://api.altertable.ai/identify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "environment": "production",
  "distinct_id": "u_01jza857w4f23s1hf2s61befmw",
  "traits": {"email": "john.doe@example.com", "plan": "premium"}
}'
```

### Response (200 OK)

```json
{
"ok": true,
"task_id": "019d68d4-c4cb-7e40-ad50-fd5bd4956505",
"error_code": null
}
```

## POST `/alias`

Merge an existing distinct ID into the user ID that should remain.

**Authentication:** Public Product Analytics API key\
**Permission:** Write identities

| Parameter     | Type    | Location | Required | Description                                     |
| ------------- | ------- | -------- | -------- | ----------------------------------------------- |
| `sync`        | boolean | query    | No       | Wait for the task to finish before returning.   |
| `environment` | string  | body     | Yes      | Environment slug such as production or staging. |
| `distinct_id` | string  | body     | Yes      | Existing ID to merge into the target user.      |
| `new_user_id` | string  | body     | Yes      | Target user ID that should remain.              |

Use `/alias` only when changing ID systems or attaching an external identifier to a known user. Most login and signup flows should use [`/identify`](/docs/api-references/product-analytics.md#post-identify).

The API also accepts an array of alias payloads for batch merges. The endpoint can return `environment-not-found` when the target environment does not exist.

### Request

```curl
curl -X POST https://api.altertable.ai/alias \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "environment":"production",
  "distinct_id":"legacy:user_123",
  "new_user_id":"u_01jza857w4f23s1hf2s61befmw"
}'
```

### Response (200 OK)

```json
{
"ok": true,
"task_id": "019d68d4-c4cb-7e40-ad50-fd5bd4956505",
"error_code": null
}
```

## Best practices

- **Use SDKs when possible**: The SDKs handle retries, queuing, and session management automatically
- **Identify users for better tracking**: Use [POST /identify](/docs/api-references/product-analytics.md#post-identify) to set user context, then include `distinct_id` in subsequent track requests to associate events with users
- **Batch payloads when needed**: Endpoints accept either a single payload or an array
- **Use consistent event names**: Use clear, consistent naming conventions for events
- **Validate data**: Ensure event properties and user traits are valid JSON
- **Send the environment explicitly**: Each endpoint requires an `environment` field in the request body

## Next steps

- **[Use SDKs](/docs/ingest-data/product-analytics.md#sdk-quick-start)**: Simplify integration with JavaScript or React SDKs
- **[Track Events](/docs/ingest-data/product-analytics/track.md)**: Learn more about event tracking
- **[Identify Users](/docs/ingest-data/product-analytics/identify.md)**: Learn more about user identification
- **[Alias Users](/docs/ingest-data/product-analytics/alias.md)**: Learn when to merge identifiers