> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/mixpanel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP API Ingestion

> Send events directly to Mixpanel using our HTTP API

# HTTP API Ingestion

Mixpanel's HTTP API provides direct access to ingest events and user profile data. Use the API when:

* You need to send data from a language without an SDK
* You want full control over the data being sent
* You're building a custom integration
* You need to import historical data

## API Endpoints

Mixpanel provides different API endpoints based on your project's data residency:

| Data Residency | Base URL                      |
| -------------- | ----------------------------- |
| US (default)   | `https://api.mixpanel.com`    |
| EU             | `https://api-eu.mixpanel.com` |
| India          | `https://api-in.mixpanel.com` |

<Warning>
  Projects with India Data Residency **must** use `api-in.mixpanel.com` or events will be rejected.
</Warning>

## Track Events: `/track`

The `/track` endpoint ingests events in real-time. Use this for events that occurred within the last 5 days.

### Endpoint

```
POST https://api.mixpanel.com/track
```

### Request Format

**Headers:**

```
Content-Type: application/json
accept: text/plain
```

**Body:**

```json theme={null}
[
  {
    "event": "Sign Up",
    "properties": {
      "token": "YOUR_PROJECT_TOKEN",
      "distinct_id": "user_123",
      "time": 1618716477000,
      "$insert_id": "unique_event_id_123",
      "plan": "premium",
      "source": "landing_page"
    }
  }
]
```

### Required Properties

| Property                 | Type   | Description                    |
| ------------------------ | ------ | ------------------------------ |
| `event`                  | String | The name of the event          |
| `properties.token`       | String | Your Mixpanel project token    |
| `properties.distinct_id` | String | Unique identifier for the user |

### Common Optional Properties

| Property                | Type   | Description                                       |
| ----------------------- | ------ | ------------------------------------------------- |
| `properties.time`       | Number | Unix timestamp (milliseconds) when event occurred |
| `properties.$insert_id` | String | Unique ID to deduplicate events                   |
| `properties.ip`         | String | IP address for geolocation (use `0` to skip)      |
| `properties.$device_id` | String | Device identifier for anonymous tracking          |

### Example: Track Event with cURL

```bash theme={null}
curl --request POST \
  --url https://api.mixpanel.com/track \
  --header 'Content-Type: application/json' \
  --header 'accept: text/plain' \
  --data '[
    {
      "event": "Video Watched",
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_456",
        "time": 1698023982000,
        "video_title": "Getting Started",
        "duration": 120,
        "completion_rate": 0.85
      }
    }
  ]'
```

### Example: Track Event with Python

```python theme={null}
import requests
import json
import time

url = "https://api.mixpanel.com/track"

payload = [{
    "event": "Purchase Completed",
    "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_789",
        "time": int(time.time() * 1000),
        "$insert_id": "purchase_xyz_123",
        "product_name": "Premium Subscription",
        "price": 29.99,
        "currency": "USD"
    }
}]

headers = {
    "Content-Type": "application/json",
    "accept": "text/plain"
}

response = requests.post(url, json=payload, headers=headers)
print(f"Status: {response.status_code}")
print(f"Response: {response.text}")
```

### Batch Tracking

You can send multiple events in a single request:

```json theme={null}
[
  {
    "event": "Page Viewed",
    "properties": {
      "token": "YOUR_PROJECT_TOKEN",
      "distinct_id": "user_123",
      "page": "Home"
    }
  },
  {
    "event": "Button Clicked",
    "properties": {
      "token": "YOUR_PROJECT_TOKEN",
      "distinct_id": "user_123",
      "button_name": "Sign Up"
    }
  }
]
```

<Note>
  Maximum batch size is 2000 events per request.
</Note>

## Import Historical Events: `/import`

The `/import` endpoint is designed for importing events older than 5 days. It requires authentication with your API secret.

### Endpoint

```
POST https://api.mixpanel.com/import
```

### Authentication

Use HTTP Basic Auth with your API secret:

```bash theme={null}
curl --request POST \
  --url https://api.mixpanel.com/import \
  --user "YOUR_API_SECRET:" \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "event": "Historical Event",
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_123",
        "time": 1609459200000
      }
    }
  ]'
```

### Find Your API Secret

1. Navigate to [Project Settings](https://mixpanel.com/settings/project)
2. Under **Access Keys**, find your **Project Secret**

<Warning>
  Keep your API secret confidential. Never expose it in client-side code or public repositories.
</Warning>

### Example: Import with Python

```python theme={null}
import requests
import base64

url = "https://api.mixpanel.com/import"
api_secret = "YOUR_API_SECRET"

payload = [{
    "event": "Historical Purchase",
    "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_456",
        "time": 1609459200000,  # Jan 1, 2021
        "product": "Annual Plan",
        "amount": 299.00
    }
}]

# Create Basic Auth header
auth_string = f"{api_secret}:"
auth_bytes = auth_string.encode('ascii')
auth_b64 = base64.b64encode(auth_bytes).decode('ascii')

headers = {
    "Content-Type": "application/json",
    "Authorization": f"Basic {auth_b64}"
}

response = requests.post(url, json=payload, headers=headers)
print(f"Status: {response.status_code}")
print(f"Response: {response.text}")
```

## User Profile Updates: `/engage`

The `/engage` endpoint updates user profile properties.

### Endpoint

```
POST https://api.mixpanel.com/engage
```

### Set Profile Properties

```bash theme={null}
curl --request POST \
  --url https://api.mixpanel.com/engage \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "$token": "YOUR_PROJECT_TOKEN",
      "$distinct_id": "user_123",
      "$set": {
        "$name": "Jane Doe",
        "$email": "jane@example.com",
        "plan": "premium",
        "signup_date": "2024-01-15"
      }
    }
  ]'
```

### Profile Operations

| Operation   | Description                             |
| ----------- | --------------------------------------- |
| `$set`      | Set properties (overwrites existing)    |
| `$set_once` | Set properties only if they don't exist |
| `$add`      | Increment numeric properties            |
| `$append`   | Append to list properties               |
| `$union`    | Add to list only if not present         |
| `$remove`   | Remove from list properties             |
| `$unset`    | Delete properties                       |
| `$delete`   | Delete the entire profile               |

### Example: Multiple Operations

```python theme={null}
import requests

url = "https://api.mixpanel.com/engage"

payload = [{
    "$token": "YOUR_PROJECT_TOKEN",
    "$distinct_id": "user_789",
    "$set": {
        "$name": "John Smith",
        "$email": "john@example.com"
    },
    "$set_once": {
        "first_seen": "2024-01-15"
    },
    "$add": {
        "login_count": 1,
        "total_revenue": 29.99
    },
    "$append": {
        "recent_purchases": "Premium Plan"
    }
}]

headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

## Identity Management

### Simplified ID Merge

Link an anonymous user to a known user ID:

```bash theme={null}
curl --request POST \
  --url https://api.mixpanel.com/track \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "event": "$identify",
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_123",
        "$anon_id": "device_xyz"
      }
    }
  ]'
```

This merges all events from `device_xyz` into the `user_123` profile.

## Best Practices

<AccordionGroup>
  <Accordion title="Use $insert_id to Prevent Duplicates">
    Include a unique `$insert_id` for each event to prevent duplicates:

    ```json theme={null}
    {
      "event": "Purchase",
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_123",
        "$insert_id": "purchase_abc_123",
        "amount": 99.99
      }
    }
    ```

    Mixpanel will reject duplicate events with the same `$insert_id` within 24 hours.
  </Accordion>

  <Accordion title="Handle IP Address for Geolocation">
    By default, Mixpanel uses the request IP for geolocation. For server-side tracking:

    **Pass the client IP:**

    ```json theme={null}
    {
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_123",
        "ip": "192.168.1.1"
      }
    }
    ```

    **Skip geolocation:**

    ```json theme={null}
    {
      "properties": {
        "token": "YOUR_PROJECT_TOKEN",
        "distinct_id": "user_123",
        "ip": "0"
      }
    }
    ```
  </Accordion>

  <Accordion title="Batch Requests for Performance">
    Send multiple events in a single request to improve performance:

    * Maximum 2000 events per batch
    * Reduces network overhead
    * Improves throughput

    ```python theme={null}
    # Batch multiple events
    events = []
    for user_action in user_actions:
        events.append({
            "event": user_action["event_name"],
            "properties": {
                "token": "YOUR_PROJECT_TOKEN",
                "distinct_id": user_action["user_id"],
                # ... other properties
            }
        })

    # Send in batches of 500
    batch_size = 500
    for i in range(0, len(events), batch_size):
        batch = events[i:i+batch_size]
        requests.post("https://api.mixpanel.com/track", json=batch)
    ```
  </Accordion>

  <Accordion title="Handle Rate Limits">
    Mixpanel has rate limits to ensure service reliability:

    * **60 requests per hour** for the `/import` endpoint per project
    * **2000 events per request** maximum batch size

    Implement exponential backoff for retries:

    ```python theme={null}
    import time
    import requests

    def send_with_retry(url, payload, max_retries=3):
        for attempt in range(max_retries):
            response = requests.post(url, json=payload)
            
            if response.status_code == 200:
                return response
            
            if response.status_code == 429:  # Rate limited
                wait_time = (2 ** attempt) * 1  # Exponential backoff
                print(f"Rate limited. Waiting {wait_time}s...")
                time.sleep(wait_time)
            else:
                print(f"Error: {response.status_code} - {response.text}")
                break
        
        return None
    ```
  </Accordion>

  <Accordion title="Validate Responses">
    Always check the API response:

    **Success Response:**

    ```json theme={null}
    {
      "status": 1,
      "error": null
    }
    ```

    **Error Response:**

    ```json theme={null}
    {
      "status": 0,
      "error": "Invalid token"
    }
    ```

    ```python theme={null}
    response = requests.post(url, json=payload)

    if response.status_code == 200:
        result = response.json()
        if result.get("status") == 1:
            print("Success!")
        else:
            print(f"Error: {result.get('error')}")
    else:
        print(f"HTTP Error: {response.status_code}")
    ```
  </Accordion>
</AccordionGroup>

## Common Issues & Troubleshooting

<AccordionGroup>
  <Accordion title="Events not appearing in Mixpanel">
    1. **Check project token**: Verify you're using the correct token
    2. **Verify data residency**: Ensure you're using the correct API endpoint
    3. **Check timestamp**: Events older than 5 days won't be accepted by `/track`
    4. **Look for errors**: Check the API response for error messages
    5. **View Events page**: Check the [Events](https://mixpanel.com/report/events) view in Mixpanel
  </Accordion>

  <Accordion title="401 Unauthorized errors">
    * For `/import` endpoint: Verify your API secret is correct
    * Check that you're using Basic Auth with the API secret
    * Ensure the API secret hasn't been regenerated
  </Accordion>

  <Accordion title="413 Request Entity Too Large">
    * Your batch is too large (>2000 events)
    * Split into smaller batches
    * Individual events might be too large (>2MB)
  </Accordion>

  <Accordion title="Geolocation is wrong">
    * If tracking from a server, pass the client's IP in the `ip` property
    * Set `ip` to `"0"` to skip geolocation
    * See [geolocation best practices](/docs/tracking-best-practices/geolocation)
  </Accordion>
</AccordionGroup>

## API Reference

For complete API documentation, see:

* [Track Event API](https://developer.mixpanel.com/reference/track-event)
* [Import Events API](https://developer.mixpanel.com/reference/import-events)
* [Engage API (User Profiles)](https://developer.mixpanel.com/reference/profile-set)
* [Identity Management API](https://developer.mixpanel.com/reference/create-identity)

## Next Steps

<CardGroup cols={2}>
  <Card title="Best Practices" icon="lightbulb" href="/data-ingestion/best-practices">
    Learn implementation best practices
  </Card>

  <Card title="View SDKs" icon="code" href="/data-ingestion/sdks">
    Use our SDKs for easier implementation
  </Card>
</CardGroup>
