Skip to main content

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 ResidencyBase URL
US (default)https://api.mixpanel.com
EUhttps://api-eu.mixpanel.com
Indiahttps://api-in.mixpanel.com
Projects with India Data Residency must use api-in.mixpanel.com or events will be rejected.

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:
[
  {
    "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

PropertyTypeDescription
eventStringThe name of the event
properties.tokenStringYour Mixpanel project token
properties.distinct_idStringUnique identifier for the user

Common Optional Properties

PropertyTypeDescription
properties.timeNumberUnix timestamp (milliseconds) when event occurred
properties.$insert_idStringUnique ID to deduplicate events
properties.ipStringIP address for geolocation (use 0 to skip)
properties.$device_idStringDevice identifier for anonymous tracking

Example: Track Event with cURL

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

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:
[
  {
    "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"
    }
  }
]
Maximum batch size is 2000 events per request.

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:
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
  2. Under Access Keys, find your Project Secret
Keep your API secret confidential. Never expose it in client-side code or public repositories.

Example: Import with Python

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

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": "[email protected]",
        "plan": "premium",
        "signup_date": "2024-01-15"
      }
    }
  ]'

Profile Operations

OperationDescription
$setSet properties (overwrites existing)
$set_onceSet properties only if they don’t exist
$addIncrement numeric properties
$appendAppend to list properties
$unionAdd to list only if not present
$removeRemove from list properties
$unsetDelete properties
$deleteDelete the entire profile

Example: Multiple Operations

import requests

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

payload = [{
    "$token": "YOUR_PROJECT_TOKEN",
    "$distinct_id": "user_789",
    "$set": {
        "$name": "John Smith",
        "$email": "[email protected]"
    },
    "$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:
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

Include a unique $insert_id for each event to prevent duplicates:
{
  "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.
By default, Mixpanel uses the request IP for geolocation. For server-side tracking:Pass the client IP:
{
  "properties": {
    "token": "YOUR_PROJECT_TOKEN",
    "distinct_id": "user_123",
    "ip": "192.168.1.1"
  }
}
Skip geolocation:
{
  "properties": {
    "token": "YOUR_PROJECT_TOKEN",
    "distinct_id": "user_123",
    "ip": "0"
  }
}
Send multiple events in a single request to improve performance:
  • Maximum 2000 events per batch
  • Reduces network overhead
  • Improves throughput
# 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)
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:
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
Always check the API response:Success Response:
{
  "status": 1,
  "error": null
}
Error Response:
{
  "status": 0,
  "error": "Invalid token"
}
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}")

Common Issues & Troubleshooting

  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 view in Mixpanel
  • 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
  • Your batch is too large (>2000 events)
  • Split into smaller batches
  • Individual events might be too large (>2MB)
  • 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

API Reference

For complete API documentation, see:

Next Steps

Best Practices

Learn implementation best practices

View SDKs

Use our SDKs for easier implementation

Build docs developers (and LLMs) love