# ExportComments API — Complete Documentation
> Export comments, reviews, followers, and other data from 40+ social media platforms via a REST API, CLI tool, or MCP server for AI assistants.
Base URL: https://exportcomments.com
API Version: v3
Authentication: X-AUTH-TOKEN header
# ExportComments API
Export comments, reviews, followers, and other data from 20+ social media platforms via a simple REST API.
## Quick Start
Get up and running in 5 steps:
1. **Sign up** at [app.exportcomments.com](https://app.exportcomments.com) and subscribe to a Premium or Business plan
2. **Get your API token** from the [API dashboard](https://app.exportcomments.com/user/api)
3. **Create an export** by sending a POST request to `/api/v3/job`
4. **Monitor progress** using webhooks or polling
5. **Download results** when the job status is `done`
## Base URL
All API requests should be made to:
```
https://exportcomments.com
```
## Content Type
All requests and responses use JSON:
```
Content-Type: application/json
```
## Your First Export
**curl:**
```
curl -X POST https://exportcomments.com/api/v3/job \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/v3/job",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}
)
job = response.json()
print(f"Job created: {job['guid']}")
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v3/job', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
}),
});
const job = await response.json();
console.log(`Job created: ${job.guid}`);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v3/job');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
echo "Job created: " . $response['guid'];
```
**Response (HTTP 201):**
```json
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
```
## What's Next
- [Authentication](/authentication) - Learn about API token authentication
- [Export Jobs](/jobs) - Full reference for creating and managing exports
- [Webhooks](/webhooks) - Get real-time notifications when exports complete
- [CLI & MCP Server](/cli) - Use ExportComments from the command line or with AI assistants
- [Sandbox Mode](/sandbox) - Test the API without consuming your quota
- [API Tiers](/tiers) - Compare Premium and Business plans
---
# Authentication
All API requests require authentication via the `X-AUTH-TOKEN` header.
## Getting Your API Token
1. Go to the [API dashboard](https://app.exportcomments.com/user/api)
2. Generate a new API token
3. Copy and store the token securely - it is only shown once
> **Store your token securely:**
The full API token is only displayed once when generated. If you lose it, you'll need to generate a new one. Never expose tokens in client-side code, public repositories, or logs.
## Making Authenticated Requests
Include the `X-AUTH-TOKEN` header in every API request:
**curl:**
```
curl https://exportcomments.com/api/v1/ping \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.get(
"https://exportcomments.com/api/v1/ping",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/ping', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/ping');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
```
## Token Properties
| Property | Details |
|----------|---------|
| Format | 120-character alphanumeric string |
| Expiration | 1 year from generation |
| Tier | Inherits the plan tier (Premium or Business) at generation time |
| Scope | Full API access for the associated user account |
## Authentication Errors
| Status Code | Error Code | Description |
|-------------|-----------|-------------|
| 401 | `AUTH_HEADER_TOKEN` | Missing or invalid `X-AUTH-TOKEN` header |
| 403 | `PLAN_EXPIRED` | Your subscription has expired |
| 403 | `TOKEN_EXPIRED` | The API token has expired |
---
# Errors
The API uses standard HTTP status codes and returns structured error responses.
## Error Response Format
All errors return a JSON object with these fields:
```json
{
"status_code": 400,
"error_code": "INVALID_REQUEST",
"detail": "The URL provided is not supported."
}
```
## HTTP Status Codes
| Code | Meaning |
|------|---------|
| 200 | Success |
| 201 | Resource created |
| 204 | Deleted (no content) |
| 400 | Bad request - invalid parameters |
| 401 | Unauthorized - missing or invalid token |
| 403 | Forbidden - insufficient permissions or expired plan |
| 404 | Not found |
| 422 | Validation error |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
## Error Codes
| Error Code | Status | Description |
|-----------|--------|-------------|
| `INVALID_REQUEST` | 400 | The request body or parameters are invalid |
| `INVALID_URL` | 400 | The provided URL is not supported or malformed |
| `AUTH_HEADER_TOKEN` | 401 | Missing or invalid X-AUTH-TOKEN header |
| `TOKEN_EXPIRED` | 403 | The API token has expired |
| `PLAN_EXPIRED` | 403 | Your subscription has expired |
| `PLATFORM_NOT_ALLOWED` | 403 | Platform not available on your tier |
| `DAILY_LIMIT_EXCEEDED` | 429 | Daily request limit exceeded |
| `RATE_LIMIT_EXCEEDED` | 429 | Per-minute rate limit exceeded |
| `CONCURRENCY_RATE_LIMIT` | 429 | Too many concurrent exports |
| `QUEUE_LIMIT_EXCEEDED` | 429 | Too many queued exports |
| `RATE_WINDOW_EXCEEDED` | 429 | Too many job creations in the sliding window |
## Rate Limit Error Response
When rate limited, the response includes a `seconds_to_wait` field:
```json
{
"status_code": 429,
"error_code": "RATE_LIMIT_EXCEEDED",
"seconds_to_wait": 60,
"detail": "Rate limit exceeded. Please wait 60 seconds."
}
```
---
# Rate Limits
The API enforces rate limits to ensure fair usage. Limits vary by tier.
## Limits by Tier
| Limit | Premium | Business |
|-------|---------|----------|
| Requests per minute | 60 | 200 |
| Daily export creations | 100 | Unlimited |
| Concurrent exports | 1 | 5 |
| Max queued exports | 3 | 5 |
| Export creations per 3 minutes | 7 | 30 |
| Jobs per 5-minute window | 10 | 15 |
## Rate Limit Headers
Rate-limited responses return HTTP 429 with:
```json
{
"status_code": 429,
"error_code": "RATE_LIMIT_EXCEEDED",
"seconds_to_wait": 45,
"detail": "Rate limit exceeded. Please wait before retrying."
}
```
## Retry Strategy
> **Recommended approach:**
Use the `seconds_to_wait` value from 429 responses to implement backoff. For polling job status, use 5-10 second intervals instead of continuous polling.
1. Check the `seconds_to_wait` field in 429 responses
2. Wait the specified duration before retrying
3. Use exponential backoff as a fallback strategy
4. Consider using [webhooks](/webhooks) instead of polling to avoid rate limits
---
# Sandbox Mode
Test API endpoints without consuming your quota or triggering real exports by adding the `X-Sandbox` header.
## Enabling Sandbox Mode
Add the `X-Sandbox: true` header to any request:
**curl:**
```
curl -X POST https://exportcomments.com/api/v3/job \
-H "X-AUTH-TOKEN: your-api-key" \
-H "X-Sandbox: true" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
```
**python:**
```
response = requests.post(
"https://exportcomments.com/api/v3/job",
headers={
"X-AUTH-TOKEN": "your-api-key",
"X-Sandbox": "true"
},
json={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}
)
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v3/job', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'X-Sandbox': 'true',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
}),
});
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v3/job');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'X-Sandbox: true',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
```
## Sandbox Behavior
- Returns instant sample data (no actual scraping)
- Not counted against daily limits
- Not rate limited
- Platform-specific data structure based on URL
- Maximum 100 comments per request
- Sandbox responses include `"sandbox": true`
- Each comment has `"_sandbox": true` flag
> **Ideal for development:**
Use sandbox mode during development and testing to build your integration without consuming quota or waiting for real exports to complete.
---
# Pagination
List endpoints support pagination using `page` and `limit` query parameters.
## Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | integer | 1 | Page number (1-indexed) |
| `limit` | integer | 10-30 | Items per page (max 30) |
## Example
**curl:**
```
curl "https://exportcomments.com/api/v3/jobs?page=2&limit=10" \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v3/jobs",
headers={"X-AUTH-TOKEN": "your-api-key"},
params={"page": 2, "limit": 10}
)
```
**node:**
```
const response = await fetch(
'https://exportcomments.com/api/v3/jobs?page=2&limit=10',
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v3/jobs?page=2&limit=10');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
```
The response returns an array of items. Iterate through pages until you receive fewer items than the `limit` value.
---
# Supported Platforms
ExportComments supports 40+ platforms. Platform availability depends on your API tier.
## Premium Tier Platforms [premium]
These platforms are available on both Premium and Business plans:
| Platform | Example URL |
|----------|-------------|
| YouTube | `youtube.com/watch?v=...` |
| Instagram | `instagram.com/p/...` |
| Facebook | `facebook.com/...` |
| TikTok | `tiktok.com/@user/video/...` |
| Twitter/X | `twitter.com/user/status/...` |
| Reddit | `reddit.com/r/subreddit/comments/...` |
| LinkedIn | `linkedin.com/posts/...` |
## Business Tier Platforms [business]
These additional platforms require a Business plan:
| Platform | Example URL |
|----------|-------------|
| Amazon | `amazon.com/dp/...` |
| Trustpilot | `trustpilot.com/review/...` |
| Google Reviews | `google.com/maps/place/...` |
| Etsy | `etsy.com/listing/...` |
| Discord | `discord.com/channels/...` |
| Steam | `steamcommunity.com/app/...` |
| Tripadvisor | `tripadvisor.com/...` |
| Yelp | `yelp.com/biz/...` |
| Airbnb | `airbnb.com/rooms/...` |
| Vimeo | `vimeo.com/...` |
| Twitch | `twitch.tv/...` |
| Apple App Store | `apps.apple.com/...` |
| AppGallery | `appgallery.huawei.com/...` |
| Product Hunt | `producthunt.com/...` |
| Kickstarter | `kickstarter.com/projects/...` |
| Indiegogo | `indiegogo.com/projects/...` |
| Walmart | `walmart.com/ip/...` |
| Best Buy | `bestbuy.com/site/...` |
| AliExpress | `aliexpress.com/item/...` |
| Flipkart | `flipkart.com/...` |
| Shopee | `shopee.com/...` |
| Lazada | `lazada.com/...` |
| IMDB | `imdb.com/title/...` |
| Bilibili | `bilibili.com/video/...` |
| Kuaishou | `kuaishou.com/...` |
| Chrome Web Store | `chromewebstore.google.com/detail/...` |
| Figma | `figma.com/community/...` |
| Expedia | `expedia.com/...` |
| Threads | `threads.net/@user/post/...` |
| VK | `vk.com/...` |
| Douyin | `douyin.com/...` |
| Rutube | `rutube.ru/video/...` |
| Change.org | `change.org/p/...` |
| Yahoo | `yahoo.com/...` |
| Newsweek | `newsweek.com/...` |
> **Platform detection:**
The API automatically detects the platform from the URL you provide. No need to specify the platform manually.
---
# API Tiers
API access requires a Premium or Business subscription. Compare the features below.
## Feature Comparison
| Feature | Premium | Business |
|---------|---------|----------|
| Daily export creations | 100 | Unlimited |
| Export creation rate | 7 per 3 minutes | 30 per 3 minutes |
| Rate window | 10 jobs per 5 minutes | 15 jobs per 5 minutes |
| Rate limit | 60/minute | 200/minute |
| Concurrent exports | 1 | 5 |
| Max queued exports | 3 | 5 |
| Max comments per export | 5,000 | 250,000 |
| Webhooks | 1 | 10 |
| Scheduled exports | 3 | 20 |
| Platforms | 7 major (YouTube, Instagram, Facebook, TikTok, Twitter, Reddit, LinkedIn) | All 40+ |
| Export formats | CSV, XLSX | JSON, CSV, XLSX |
| Custom cron schedules | No | Yes |
| Hourly schedules | No | Yes |
| Priority processing | Yes | Yes |
## Scheduled Export Frequencies
| Frequency | Premium | Business |
|-----------|---------|----------|
| Hourly | No | Yes |
| Daily | Yes | Yes |
| Weekly | Yes | Yes |
| Monthly | Yes | Yes |
| Custom (cron) | No | Yes |
## Getting Started
1. Choose a plan at [exportcomments.com/pricing](https://exportcomments.com/pricing)
2. Generate an API token from your [dashboard](https://app.exportcomments.com/user/api)
3. Start making API requests
> **Upgrade anytime:**
You can upgrade from Premium to Business at any time. Your existing tokens will automatically gain Business tier access.
---
# Sentiment Analysis
Automatically classify exported comments as **positive**, **negative**, or **neutral** using AI-powered sentiment analysis. Choose between multiple AI providers or use the free built-in analyzer.
## How It Works
When you enable sentiment analysis on an export, each comment's text is sent to your configured AI provider for classification. The results are embedded directly in your exported file with color-coded indicators:
| Sentiment | Color | Description |
|-----------|-------|-------------|
| Positive | Green | Favorable, supportive feedback |
| Negative | Red | Critical, dissatisfied comments |
| Neutral | Yellow | Informational, no strong opinion |
Excel exports include a **Sentiment Legend** sheet with full descriptions of each classification.
## AI Providers
You can choose between three providers. Configure your preference in the [API dashboard](https://app.exportcomments.com/user/api).
### OpenAI {% badge type="info" %}Default{% /badge %}
- **Model:** GPT-3.5 Turbo
- **Best for:** Fast, cost-effective analysis
- **Key format:** Starts with `sk-`
- **Get a key:** [platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)
### Claude (Anthropic)
- **Model:** Claude Haiku
- **Best for:** Accurate, nuanced understanding of context and sarcasm
- **Key format:** Starts with `sk-ant-`
- **Get a key:** [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)
### Google Cloud Natural Language
- **Model:** Cloud Natural Language API v2
- **Best for:** Purpose-built sentiment scoring (no prompt engineering, returns a numeric score)
- **Key format:** Google Cloud API key
- **Get a key:** [console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials)
> **Google NLP difference:**
Unlike OpenAI and Claude which use prompt-based analysis, Google Cloud NLP has a dedicated sentiment endpoint that returns a score from -1.0 to 1.0. This tends to produce more consistent results for pure sentiment classification.
## Free Mode
Users without a configured API key get basic sentiment analysis at no cost:
| Limit | Value |
|-------|-------|
| Max comments analyzed | 10 per export |
| Max characters per comment | 50 |
| Language support | English only |
| Engine | TextBlob (text-based) |
Free mode is useful for testing. For production use, configure an AI provider for unlimited multilingual analysis.
## Setup
### 1. Configure your provider
```
Dashboard > API > AI Sentiment Analysis
```
Select your preferred provider, paste your API key, and save. Your key is stored encrypted.
### 2. Run analysis on an export
After an export completes, click **Run Sentiment Analysis** on the results page. The button shows which provider will be used:
- `Run Sentiment Analysis via OpenAI`
- `Run Sentiment Analysis via Claude (Anthropic)`
- `Run Sentiment Analysis via Google Cloud NLP`
- `Run Sentiment Analysis [ FREE ]` (no API key configured)
### 3. Download results
The exported file includes sentiment values color-coded in the spreadsheet. Use Excel's filter on the Sentiment column to isolate specific sentiments.
## API Usage
Sentiment analysis is triggered as a filter option when creating or re-processing export jobs. It runs automatically during the export filter pipeline.
### Filter option
Include `sentiment: true` in your filter options when creating a filtered export:
**curl:**
```bash
curl -X POST https://exportcomments.com/api/v3/job \
-H "X-AUTH-TOKEN: your-api-token" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.youtube.com/watch?v=example",
"options": {
"filter": {
"sentiment": true
}
}
}'
```
The sentiment provider and API key configured in your account settings will be used automatically.
## Supported Platforms
Sentiment analysis works on all platforms that export text content, including:
Facebook, Instagram, YouTube, TikTok, Twitter/X, Reddit, Discord, LinkedIn, Amazon, Google Reviews, Trustpilot, Steam, Etsy, Shopee, Lazada, Tripadvisor, Vimeo, Twitch, ProductHunt, Kickstarter, and 20+ more.
> **Not supported:**
Sentiment analysis is not available for non-text exports such as followers, following, likes, and shares.
## Cost Estimates
ExportComments does not charge for sentiment analysis. Costs come from your AI provider's API pricing:
| Provider | Approximate Cost | Free Tier |
|----------|-----------------|-----------|
| OpenAI (GPT-3.5 Turbo) | ~$0.002 / 1,000 comments | None |
| Claude (Haiku) | ~$0.001 / 1,000 comments | None |
| Google Cloud NLP | ~$1.00 / 1,000 comments | First 5,000/month free |
> **Switching providers:**
You can switch providers anytime from the API dashboard. To revert to the free analyzer, remove your API key.
---
# AI Agents
ExportComments provides first-class support for AI agents through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). AI assistants like Claude, Cursor, and Windsurf can export comments and reviews from 40+ platforms using natural language.
## Quick Start
### 1. Install the package
```bash
npm install -g exportcomments-cli
```
### 2. Get your API token
Sign up at [exportcomments.com](https://exportcomments.com) and get your token from the [API dashboard](https://app.exportcomments.com/user/api).
### 3. Configure your AI client
Add ExportComments as an MCP server. Example for Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"exportcomments": {
"command": "npx",
"args": ["-y", "exportcomments-cli"],
"env": {
"EXPORTCOMMENTS_API_TOKEN": "your-token-here"
}
}
}
}
```
### 4. Start using it
Ask your AI assistant:
> "Export comments from this YouTube video: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
The AI will use the MCP tools to create the export, wait for completion, and return the results.
## npm Package
The [`exportcomments-cli`](https://www.npmjs.com/package/exportcomments-cli) npm package provides both the CLI tool and the MCP server:
| Binary | Purpose |
|--------|---------|
| `exportcomments` | CLI tool for terminal usage |
| `exportcomments-cli` | Alias for the CLI tool |
| `exportcomments-mcp` | MCP server for AI agents |
**Requirements:** Node.js 18+
## Supported AI Clients
| Client | Setup Method |
|--------|-------------|
| Claude Desktop | Add to `claude_desktop_config.json` |
| Claude Code | `claude mcp add exportcomments -- npx -y exportcomments-cli` |
| Cursor | Add to `.cursor/mcp.json` |
| Windsurf | Add to MCP configuration |
| Any MCP client | Run `exportcomments-mcp` with stdio transport |
See the [MCP Server](/cli/mcp) page for detailed setup instructions for each client.
## MCP Tools
The MCP server exposes 6 tools that AI agents can call:
| Tool | Description |
|------|-------------|
| `export_comments` | Create an export job for any supported URL |
| `check_export` | Check job status and wait for completion |
| `list_exports` | List all export jobs with pagination |
| `download_export` | Download raw JSON data for completed jobs |
| `detect_platform` | Identify platform from a URL |
| `list_platforms` | List all 33+ supported platforms |
See [MCP Server — Tool Parameters](/cli/mcp#tool-parameters) for the full parameter reference.
## Supported Platforms
ExportComments supports 33+ platforms across social media, e-commerce, and review sites:
**Social Media:** Instagram, YouTube, TikTok, Facebook, Twitter/X, LinkedIn, Reddit, Threads, VK
**Video & Messaging:** Twitch, Vimeo, Discord
**E-Commerce:** Amazon, AliExpress, Shopee, Lazada, Flipkart, Etsy, Walmart, Best Buy, eBay
**Reviews:** Trustpilot, Yelp, Google Reviews, TripAdvisor, IMDb, Airbnb, Steam
**Other:** Apple App Store, Google Play Store, Disqus, Product Hunt, Change.org
Use the `detect_platform` tool to check if a URL is supported and see available options, or `list_platforms` to browse all platforms.
## Use Cases for AI Agents
### Sentiment Analysis
Export comments from a product page, then analyze sentiment patterns, identify common complaints, and summarize customer feedback.
### Competitive Research
Export reviews from competitor products across Amazon, Trustpilot, and app stores, then compare sentiment and feature requests.
### Social Media Monitoring
Export comments from social media posts to track brand mentions, engagement patterns, and audience reactions.
### Content Research
Export YouTube or Reddit comments to understand audience interests and generate content ideas based on what people discuss.
### Market Research
Export reviews from multiple e-commerce platforms to identify market gaps, pricing sentiment, and feature demand.
## Example Prompts
Here are example prompts you can use with AI agents that have the ExportComments MCP server configured:
- "Export all comments from this Instagram post and summarize the main themes"
- "Get the reviews for this Amazon product and tell me the top 5 complaints"
- "Export comments from this YouTube video with replies included, then analyze the sentiment"
- "Check what platforms ExportComments supports for this URL"
- "Export the last 100 comments from this Reddit thread and identify the most discussed topics"
- "Get Trustpilot reviews for example.com and create a summary report"
## LLM-Friendly Documentation
This documentation is available in machine-readable formats for AI agents and LLMs:
| File | Description |
|------|-------------|
| [`/llms.txt`](/llms.txt) | Concise index of all documentation pages with descriptions |
| [`/llms-full.txt`](/llms-full.txt) | Complete documentation content in plain text format |
These files are auto-generated from the documentation source and kept in sync with every build. Use `llms.txt` for a quick overview or `llms-full.txt` for comprehensive context.
## Output Format
All MCP tool responses return structured JSON for easy parsing:
```json
{
"ok": true,
"data": { ... }
}
```
On error:
```json
{
"ok": false,
"error": "Human-readable error message",
"error_code": "MACHINE_READABLE_CODE",
"detail": "Additional context"
}
```
## Job Lifecycle
When an AI agent creates an export, the job goes through these statuses:
1. `queueing` — Job is queued for processing
2. `progress` — Job is actively extracting data
3. `done` — Job completed, data ready for download
4. `error` — Job failed (check error details)
Use `wait=true` on `export_comments` or `check_export` to automatically poll until the job reaches a terminal state. The MCP server polls every 5 seconds with a 10-minute timeout.
> **Recommended workflow:**
For the best AI agent experience, use `export_comments` with `wait=true` so the agent gets the final result in a single call, then use `download_export` to retrieve the data.
---
# API Tokens
API tokens provide programmatic access to the ExportComments API. Each token is tied to your user account and inherits your subscription tier.
## The API Token Object
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Unique token identifier |
| `expiresAt` | datetime | Token expiration date (1 year from generation) |
| `isExpired` | boolean | Whether the token is expired |
| `createdAt` | datetime | When the token was generated |
> **Token visibility:**
The full token value is only returned once during generation. After that, only metadata (uuid, expiry, etc.) is available via the API.
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v1/api_tokens/generate` | Generate a new API token |
| `GET` | `/api/v1/api_tokens` | List all tokens |
| `GET` | `/api/v1/api_tokens/{uuid}` | Retrieve a specific token |
| `DELETE` | `/api/v1/api_tokens/{uuid}` | Revoke a token |
## Token Properties
| Property | Details |
|----------|---------|
| Format | 120-character alphanumeric string prefixed with `ec_live_` |
| Expiration | 1 year from generation date |
| Scope | Full API access for the associated user account |
| Tier | Inherits the plan tier (Premium or Business) at generation time |
---
# Generate a New API Token
POST /api/v1/api_tokens/generate
Generate a new API token for programmatic API access. The server creates the token and returns a confirmation message. To retrieve the token value and details, use the [List tokens](/api-tokens/list) endpoint after generation.
## Request Body
- **name** (string, required=false): Optional display name for the token
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/api_tokens/generate \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json"
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/v1/api_tokens/generate",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
result = response.json()
print(result["message"]) # user.api.token_generated
# Retrieve the new token details via the List endpoint
tokens = requests.get(
"https://exportcomments.com/api/v1/api_tokens",
headers={"X-AUTH-TOKEN": "your-api-key"}
).json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/api_tokens/generate', {
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const data = await response.json();
console.log(data.message); // user.api.token_generated
// Retrieve the new token details via the List endpoint
const tokens = await fetch('https://exportcomments.com/api/v1/api_tokens', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}).then(r => r.json());
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/api_tokens/generate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
echo $data['message']; // user.api.token_generated
// Retrieve the new token details via the List endpoint
$ch2 = curl_init('https://exportcomments.com/api/v1/api_tokens');
curl_setopt_array($ch2, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$tokens = json_decode(curl_exec($ch2), true);
```
**Response (HTTP 200):**
```json
{
"message": "user.api.token_generated"
}
```
> **Retrieving the generated token:**
The generate endpoint only confirms that the token was created. To access the token value, UUID, and expiry details, call the [List tokens](/api-tokens/list) or [Retrieve token](/api-tokens/retrieve) endpoint after generation.
---
# List All API Tokens
GET /api/v1/api_tokens
Retrieve all API tokens for the authenticated user, ordered by expiration date (newest first).
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
**curl:**
```
curl https://exportcomments.com/api/v1/api_tokens \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.get(
"https://exportcomments.com/api/v1/api_tokens",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
tokens = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/api_tokens', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const tokens = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/api_tokens');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$tokens = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2027-02-23T12:00:00+00:00",
"isExpired": false,
"createdAt": "2026-02-23T12:00:00+00:00"
}
]
```
> **Full token not returned:**
This endpoint returns token metadata only. The full token value is never returned after initial generation. Use the `uuid` to identify and manage individual tokens.
---
# Retrieve an API Token
GET /api/v1/api_tokens/{uuid}
Retrieve details of a specific API token. The full token value is never returned after initial generation.
## Path Parameters
- **uuid** (string, required=true): API token identifier
**curl:**
```
curl https://exportcomments.com/api/v1/api_tokens/550e8400-e29b-41d4-a716-446655440000 \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.get(
f"https://exportcomments.com/api/v1/api_tokens/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
token = response.json()
```
**node:**
```
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://exportcomments.com/api/v1/api_tokens/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const token = await response.json();
```
**php:**
```
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$ch = curl_init("https://exportcomments.com/api/v1/api_tokens/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$token = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2027-02-23T12:00:00+00:00",
"isExpired": false,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# Revoke an API Token
DELETE /api/v1/api_tokens/{uuid}
Permanently delete an API token. The token immediately becomes invalid for all API requests.
## Path Parameters
- **uuid** (string, required=true): API token identifier
**curl:**
```
curl -X DELETE https://exportcomments.com/api/v1/api_tokens/550e8400-e29b-41d4-a716-446655440000 \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.delete(
f"https://exportcomments.com/api/v1/api_tokens/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
# Returns 204 No Content on success
```
**node:**
```
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://exportcomments.com/api/v1/api_tokens/${uuid}`,
{
method: 'DELETE',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
// Returns 204 No Content on success
```
**php:**
```
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$ch = curl_init("https://exportcomments.com/api/v1/api_tokens/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
// Returns 204 No Content on success
```
Returns `204 No Content` on success.
> **Irreversible:**
This action cannot be undone. Any requests using this token will immediately return `401 Unauthorized`. Generate a new token if you need continued API access.
---
# Export Jobs
Export jobs are the core of the ExportComments API. Create a job to export comments, reviews, or other data from any supported platform. The API automatically detects the platform from the URL you provide and begins processing.
## The Job Object
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Numeric job ID |
| `guid` | string (uuid) | Unique job identifier used in API requests |
| `status` | string | Current job status |
| `url` | string | Source URL being exported |
| `options` | object | Export options applied to this job |
| `locked` | boolean | Whether the job is locked |
| `json_url` | string | URL to the JSON export file (when done) |
| `download_link` | string | Direct download link for the XLSX file (when done) |
| `rawFile` | string | Raw file name |
## Job Statuses
| Status | Description |
|--------|-------------|
| `queueing` | Job is queued and waiting to be processed |
| `progress` | Job is actively exporting data |
| `done` | Job completed successfully |
| `error` | Job failed - can be retried |
| `stopped` | Job was manually stopped |
## Status Lifecycle
```
queueing → progress → done
→ error (retryable)
→ stopped
```
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v3/job` | Create a new export job |
| `GET` | `/api/v3/job/{guid}` | Retrieve a specific job |
| `GET` | `/api/v3/jobs` | List all jobs |
| `PATCH` | `/api/v3/job/{guid}/retry` | Retry a failed job |
| `PATCH` | `/api/v3/job/{guid}/stop` | Stop a running job |
## Polling vs WebSocket vs Webhooks
> **Use real-time updates for production:**
For production integrations, use [WebSocket real-time updates](/realtime) for instant progress tracking or [webhooks](/webhooks) for server-side event processing. Polling consumes rate limit quota and introduces unnecessary latency.
| Method | Best For |
|--------|----------|
| [**WebSocket**](/realtime) | Dashboards, live progress bars, interactive UIs |
| [**Webhooks**](/webhooks) | Server-to-server integrations, background processing |
| **Polling** | Simple scripts, environments without WebSocket support |
When polling, check job status every 5-10 seconds. A job transitions from `queueing` to `progress` to `done` (or `error`). Once status is `done`, the `json_url` and `download_link` fields will be populated.
---
# Create an Export Job
POST /api/v3/job
Create a new export job. The API automatically detects the platform from the URL and begins processing.
## Request Body
- **url** (string, required=true): URL to export (must be from a supported platform)
- **options** (object, required=false): Export configuration options (see below)
### Options Object
Pass additional options in the `options` field:
- **options.limit** (integer, required=false): Maximum items to export (up to your tier's max)
- **options.replies** (boolean, required=false): Include replies/nested comments (default: false)
- **options.likes** (boolean, required=false): Include likes data
- **options.shares** (boolean, required=false): Include shares data
- **options.followers** (boolean, required=false): Export followers instead of comments
- **options.following** (boolean, required=false): Export following instead of comments
- **options.tweets** (boolean, required=false): Export tweets (Twitter/X)
- **options.live** (boolean, required=false): Export live chat data
- **options.vpn** (string, required=false): VPN location name (see VPN Locations)
- **options.pool** (string (uuid), required=false): Proxy pool UUID to use
- **options.cursor** (string, required=false): Pagination cursor for continuing a previous export
- **options.minTimestamp** (integer, required=false): Minimum Unix timestamp filter
- **options.maxTimestamp** (integer, required=false): Maximum Unix timestamp filter
- **options.cookies** (object, required=false): Authentication cookies for private content
- **options.facebookAds** (boolean, required=false): Export Facebook Ads data only
- **options.advanced** (boolean, required=false): Enable advanced search (Google)
### Cookies Object
Some platforms require authentication cookies to access private or restricted content:
- **options.cookies.sessionid** (string, required=false): Session ID (Instagram, general)
- **options.cookies.auth_token** (string, required=false): Auth token (Twitter/X)
- **options.cookies.cookie_user** (string, required=false): Facebook c_user cookie
- **options.cookies.cookie_xs** (string, required=false): Facebook xs cookie
**curl:**
```
curl -X POST https://exportcomments.com/api/v3/job \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
}
}'
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/v3/job",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": True
}
}
)
job = response.json()
print(f"Job created: {job['guid']}")
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v3/job', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
options: { limit: 500, replies: true },
}),
});
const job = await response.json();
console.log(`Job created: ${job.guid}`);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v3/job');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'options' => ['limit' => 500, 'replies' => true],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
echo "Job created: " . $job['guid'];
```
**Response — Single Job (HTTP 201):**
```json
{
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084"
}
```
> **Use the Retrieve endpoint for full job details:**
The creation response only returns the `guid`. To get the full job object (status, download links, etc.) use the [Retrieve Job](/jobs/retrieve) endpoint with that `guid`.
## Batch Job Creation
Send `{"urls": [...]}` instead of `{"url": "..."}` to create multiple jobs in a single request.
**Response — Batch Jobs (HTTP 201):**
```json
{
"data": [
{
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"item": {}
},
{
"guid": "c7341e58-4249-6fae-0873-3fg0g0506195",
"item": {}
}
]
}
```
> **Job processing is asynchronous:**
A newly created job starts in `queueing` status. Use the [Retrieve Job](/jobs/retrieve) endpoint to poll for status changes, or set up a [webhook](/webhooks) to be notified when the job completes.
---
# Retrieve an Export Job
GET /api/v3/job/{guid}
Retrieve details of a specific export job by GUID. Use this endpoint to poll job status or fetch download links once a job completes.
## Path Parameters
- **guid** (string, required=true): Job GUID identifier
**curl:**
```
curl https://exportcomments.com/api/v3/job/b4219d47-3138-5efd-9762-2ef9f9495084 \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
import time
guid = "b4219d47-3138-5efd-9762-2ef9f9495084"
while True:
response = requests.get(
f"https://exportcomments.com/api/v3/job/{guid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
job = response.json()
if job['status'] == 'done':
print(f"Download: {job['download_link']}")
break
elif job['status'] == 'error':
print("Job failed")
break
time.sleep(5)
```
**node:**
```
const guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
const poll = async () => {
const response = await fetch(
`https://exportcomments.com/api/v3/job/${guid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const job = await response.json();
if (job.status === 'done') {
console.log(`Download: ${job.download_link}`);
return;
} else if (job.status === 'error') {
console.log('Job failed');
return;
}
setTimeout(poll, 5000);
};
poll();
```
**php:**
```
$guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
do {
$ch = curl_init("https://exportcomments.com/api/v3/job/{$guid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
if ($job['status'] === 'done') {
echo "Download: " . $job['download_link'];
break;
} elseif ($job['status'] === 'error') {
echo "Job failed";
break;
}
sleep(5);
} while (true);
```
**Response (completed job) (HTTP 200):**
```json
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
},
"locked": false,
"json_url": "https://exportcomments.com/exports/12345.json",
"download_link": "https://exportcomments.com/exports/12345.xlsx"
}
```
**Response (in-progress job) (HTTP 200):**
```json
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "progress",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
},
"locked": false,
"json_url": null,
"download_link": null
}
```
> **Polling interval:**
Poll at 5-10 second intervals to avoid hitting rate limits. For production workloads, use [webhooks](/webhooks) instead of polling to avoid consuming API quota.
---
# List Export Jobs
GET /api/v3/jobs
Retrieve a paginated list of export jobs for the authenticated user, ordered by creation date (newest first).
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 30, max: 30)
**curl:**
```
curl "https://exportcomments.com/api/v3/jobs?page=1&limit=10" \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.get(
"https://exportcomments.com/api/v3/jobs",
headers={"X-AUTH-TOKEN": "your-api-key"},
params={"page": 1, "limit": 10}
)
jobs = response.json()
```
**node:**
```
const response = await fetch(
'https://exportcomments.com/api/v3/jobs?page=1&limit=10',
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const jobs = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v3/jobs?page=1&limit=10');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$jobs = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
},
"locked": false,
"json_url": "https://exportcomments.com/exports/12345.json",
"download_link": "https://exportcomments.com/exports/12345.xlsx"
},
{
"id": 12344,
"guid": "a3118c36-2027-4dfc-8651-1de8e8384973",
"status": "progress",
"url": "https://www.instagram.com/p/ABC123/",
"options": {},
"locked": false,
"json_url": null,
"download_link": null
}
]
```
> **Pagination:**
Iterate through pages until the response array contains fewer items than the requested `limit`. See [Pagination](/pagination) for details.
---
# Retry an Export Job
PATCH /api/v3/job/{guid}/retry
Retry a failed export job. The job must be in `error` status. Rate limited to 100 retries per user per 30 minutes.
## Path Parameters
- **guid** (string, required=true): Job GUID identifier
**curl:**
```
curl -X PATCH https://exportcomments.com/api/v3/job/b4219d47-3138-5efd-9762-2ef9f9495084/retry \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
guid = "b4219d47-3138-5efd-9762-2ef9f9495084"
response = requests.patch(
f"https://exportcomments.com/api/v3/job/{guid}/retry",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
job = response.json()
```
**node:**
```
const guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
const response = await fetch(
`https://exportcomments.com/api/v3/job/${guid}/retry`,
{
method: 'PATCH',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
const job = await response.json();
```
**php:**
```
$guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
$ch = curl_init("https://exportcomments.com/api/v3/job/{$guid}/retry");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
},
"locked": false,
"json_url": null,
"download_link": null
}
```
> **Only failed jobs can be retried:**
This endpoint only accepts jobs in `error` status. Attempting to retry a job in any other status will return a `400` error. The retried job re-enters the queue with its original options.
---
# Stop an Export Job
PATCH /api/v3/job/{guid}/stop
Stop a running or queued export job. Cannot stop jobs that are already in `done`, `error`, or `stopped` status.
## Path Parameters
- **guid** (string, required=true): Job GUID identifier
**curl:**
```
curl -X PATCH https://exportcomments.com/api/v3/job/b4219d47-3138-5efd-9762-2ef9f9495084/stop \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
guid = "b4219d47-3138-5efd-9762-2ef9f9495084"
response = requests.patch(
f"https://exportcomments.com/api/v3/job/{guid}/stop",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
job = response.json()
```
**node:**
```
const guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
const response = await fetch(
`https://exportcomments.com/api/v3/job/${guid}/stop`,
{
method: 'PATCH',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
const job = await response.json();
```
**php:**
```
$guid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
$ch = curl_init("https://exportcomments.com/api/v3/job/{$guid}/stop");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "stopped",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"options": {
"limit": 500,
"replies": true
},
"locked": false,
"json_url": null,
"download_link": null
}
```
> **Stopped jobs cannot be resumed:**
A stopped job cannot be resumed. If you need to export the same URL again, create a new job using the [Create Job](/jobs/create) endpoint.
---
# Real-Time Updates
Get instant notifications about export job progress, completions, and failures via a persistent WebSocket connection. This is the fastest way to track export status — no polling required.
[premium] [business]
## How It Works
ExportComments uses [Centrifugo](https://centrifugal.dev/) as its real-time messaging server. Clients connect via WebSocket using the [centrifuge-js](https://github.com/centrifugal/centrifuge-js) SDK and subscribe to channels that deliver events as they happen.
```
1. Authenticate → POST /api/public/auth/socket → JWT token
2. Connect → WebSocket to wss://exportcomments.com/connection/websocket
3. Subscribe → Auto-subscribed to your user channels
4. Receive → Events pushed in real-time as exports progress
```
## Real-Time vs Webhooks vs Polling
| Method | Latency | Use Case |
|--------|---------|----------|
| **WebSocket** | Instant (~50ms) | Dashboards, live progress bars, interactive UIs |
| **Webhooks** | Near-instant | Server-to-server integrations, background processing |
| **Polling** | 5-10 seconds | Simple scripts, environments without WebSocket support |
> **WebSocket and webhooks are complementary:**
Use WebSocket for client-facing real-time updates (progress bars, notifications) and webhooks for server-side event processing (triggering pipelines, storing results). They can be used together.
## Available Channels
When you authenticate, you are automatically subscribed to these personal channels:
| Channel | Description |
|---------|-------------|
| `exports:{uuid}` | Export job events (created, progress, finished, failed) |
| `webhooks:{uuid}` | Webhook delivery events |
| `user:{uuid}` | Account-level notifications (subscription changes) |
| `user_notifications_channel:{uuid}` | Toast-style notifications |
| `user_dashboard_channel:{uuid}` | Dashboard data updates (activity, usage stats) |
Where `{uuid}` is your user UUID.
## Event Types
| Event | Channel | Description |
|-------|---------|-------------|
| `job.created` | `exports:{uuid}` | New export job started |
| `job.progress` | `exports:{uuid}` | Export progress update (throttled to every 5s) |
| `job.finished` | `exports:{uuid}` | Export completed successfully |
| `job.failed` | `exports:{uuid}` | Export failed |
| `job.requeued` | `exports:{uuid}` | Export requeued for retry |
| `job.convert.done` | `exports:{uuid}` | File conversion completed |
| `job.convert.failed` | `exports:{uuid}` | File conversion failed |
| `webhook.event.created` | `webhooks:{uuid}` | Webhook delivery attempted |
| `subscription.updated` | `user:{uuid}` | Subscription plan changed |
| `bulk_download.ready` | `exports:{uuid}` | Bulk download ZIP is ready |
| `bulk_download.failed` | `exports:{uuid}` | Bulk download failed |
## Quick Start
```javascript
import { Centrifuge } from 'centrifuge';
// 1. Create client with token authentication
const client = new Centrifuge('wss://exportcomments.com/connection/websocket', {
getToken: async () => {
const res = await fetch('https://exportcomments.com/api/public/auth/socket', {
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const data = await res.json();
return data.token;
},
});
// 2. Listen for events on your exports channel
client.on('publication', (ctx) => {
console.log('Event:', ctx.data.event, ctx.data);
});
// 3. Connect
client.connect();
```
See the [Connection Guide](/realtime/connect) for complete examples in multiple languages.
---
# Authenticate
POST /api/public/auth/socket
Obtain a JWT token for establishing a WebSocket connection. The token grants access to your personal channels based on your account.
## Request Headers
- **X-AUTH-TOKEN** (string, required=true): Your API key
- **Content-Type** (string, required=false): application/json
## Request Body
No request body is required for standard authentication. The server automatically assigns channels based on your user account.
**curl:**
```
curl -X POST https://exportcomments.com/api/public/auth/socket \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/public/auth/socket",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
data = response.json()
token = data["token"]
print(f"WebSocket token: {token}")
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/public/auth/socket', {
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const { token } = await response.json();
console.log(`WebSocket token: ${token}`);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/public/auth/socket');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
echo "WebSocket token: " . $data['token'];
```
**Response (HTTP 200):**
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
```
## Token Details
| Property | Value |
|----------|-------|
| **Format** | JWT (JSON Web Token) |
| **TTL** | 24 hours (authenticated users) |
| **Algorithm** | HS256 |
The token includes your user UUID as the subject and pre-authorizes subscription to your personal channels:
- `user:{uuid}`
- `exports:{uuid}`
- `webhooks:{uuid}`
- `user_notifications_channel:{uuid}`
- `user_dashboard_channel:{uuid}`
## Token Refresh
The Centrifuge client SDK handles token refresh automatically. When you provide a `getToken` callback, the SDK calls it whenever the token expires or the connection needs re-authentication.
```javascript
const client = new Centrifuge('wss://exportcomments.com/connection/websocket', {
getToken: async () => {
// This is called automatically when the token expires
const res = await fetch('https://exportcomments.com/api/public/auth/socket', {
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const data = await res.json();
return data.token;
},
});
```
> **No manual refresh needed:**
You do not need to implement token refresh logic yourself. The `getToken` callback is invoked by the SDK whenever a new token is required, including on initial connection and after token expiration.
---
# Channels
Channels are named streams of events that clients subscribe to. When you connect with a valid token, you are automatically subscribed to your personal channels.
## Personal Channels
These channels are scoped to your user account. The `{uuid}` is your user UUID (included in the JWT token).
### exports:{uuid}
The primary channel for tracking export jobs. Receives all job lifecycle events.
**Events delivered:**
| Event | When |
|-------|------|
| `job.created` | A new export job is created |
| `job.progress` | Export progress update (throttled to every 5 seconds) |
| `job.finished` | Export completed successfully |
| `job.failed` | Export failed |
| `job.requeued` | Export requeued for retry |
| `job.convert.done` | File format conversion completed |
| `job.convert.failed` | File format conversion failed |
| `bulk_download.ready` | Bulk download ZIP is ready |
| `bulk_download.failed` | Bulk download ZIP assembly failed |
### webhooks:{uuid}
Receives events when webhook deliveries are attempted.
**Events delivered:**
| Event | When |
|-------|------|
| `webhook.event.created` | A webhook delivery was attempted |
### user:{uuid}
Account-level notifications not tied to specific exports.
**Events delivered:**
| Event | When |
|-------|------|
| `subscription.updated` | Your subscription plan was created, expired, or canceled |
### user_notifications_channel:{uuid}
Human-readable notification messages suitable for displaying as toasts or alerts in a UI.
**Message types:**
| Type | Description |
|------|-------------|
| `export_started` | An export began processing |
| `export_completed` | An export finished successfully |
| `export_failed` | An export failed |
| `export_progress` | Progress milestone reached (25%, 50%, 75%) |
| `subscription_updated` | Subscription plan changed |
| `usage_limit_warning` | Approaching monthly usage limit (90%+) |
### user_dashboard_channel:{uuid}
Structured dashboard data updates. Useful for building real-time dashboards that mirror the ExportComments UI.
**Message types:**
| Type | Description |
|------|-------------|
| `new_activity` | A new export appeared in the activity list |
| `activity_update` | An existing export's status/progress changed |
| `complete_update` | Full dashboard data snapshot (sent on significant state changes) |
| `usage_stats` | Updated usage statistics (exports count, data processed) |
| `subscription_status` | Current subscription details |
## Job-Specific Channel
You can also subscribe to a channel for a single export job:
### export.{guid}.job
Receives events only for the specific export job identified by `{guid}`. This is useful when you want to track a single job without receiving events for all your exports.
> **Automatic subscription:**
Your personal channels (`exports:{uuid}`, `webhooks:{uuid}`, etc.) are included in the JWT token and subscribed automatically on connect. The job-specific channel `export.{guid}.job` is also available if the token includes it.
## Message Format
All messages on the `exports:{uuid}` and `webhooks:{uuid}` channels follow the standard event format:
```json
{
"version": "v3-beta",
"event": "job.finished",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://example.com/download/export.xlsx",
"details": {
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"totalExported": 1500
}
}
```
Messages on dashboard and notification channels use a different structure:
```json
{
"type": "activity_update",
"payload": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"updates": {
"id": "b4219d47-3138-5efd-9762-2ef9f9495084",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"status": "progress",
"progress": 45.2,
"totalItems": 1500,
"exportedItems": 678,
"platform": "YouTube",
"duration": "2 minutes"
}
},
"timestamp": "2025-01-15T10:30:00+00:00"
}
```
---
# Connection Guide
Connect to the ExportComments WebSocket API using the Centrifuge client SDK. The SDK handles connection management, automatic reconnection, and token refresh.
## WebSocket Endpoint
```
wss://exportcomments.com/connection/websocket
```
## JavaScript / Node.js
Install the official Centrifuge client:
```bash
npm install centrifuge
```
### Full Example
```javascript
import { Centrifuge } from 'centrifuge';
const API_KEY = 'your-api-key';
// Create client with automatic token management
const client = new Centrifuge('wss://exportcomments.com/connection/websocket', {
getToken: async () => {
const res = await fetch('https://exportcomments.com/api/public/auth/socket', {
method: 'POST',
headers: { 'X-AUTH-TOKEN': API_KEY },
});
const data = await res.json();
return data.token;
},
});
// Connection state changes
client.on('state', (ctx) => {
console.log(`State: ${ctx.oldState} → ${ctx.newState}`);
});
// Receive events from auto-subscribed channels
client.on('publication', (ctx) => {
const { event, guid, status } = ctx.data;
switch (event) {
case 'job.created':
console.log(`Export ${guid} started`);
break;
case 'job.progress':
console.log(`Export ${guid} progress: ${ctx.data.current}/${ctx.data.total}`);
break;
case 'job.finished':
console.log(`Export ${guid} completed — download: ${ctx.data.url}`);
break;
case 'job.failed':
console.log(`Export ${guid} failed`);
break;
}
});
// Handle errors
client.on('error', (err) => {
console.error('Connection error:', err);
});
// Connect
client.connect();
// To disconnect later:
// client.disconnect();
```
### Subscribing to a Specific Job
If you only need updates for a single export, subscribe to its dedicated channel:
```javascript
const jobGuid = 'b4219d47-3138-5efd-9762-2ef9f9495084';
const sub = client.newSubscription(`export.${jobGuid}.job`);
sub.on('publication', (ctx) => {
console.log('Job event:', ctx.data.event, ctx.data);
});
sub.subscribe();
```
## Python
Install the Centrifuge Python client:
```bash
pip install centrifuge-python
```
### Full Example
```python
import asyncio
import aiohttp
from centrifuge import Client, PublicationEvent
API_KEY = "your-api-key"
WS_URL = "wss://exportcomments.com/connection/websocket"
AUTH_URL = "https://exportcomments.com/api/public/auth/socket"
async def get_token():
async with aiohttp.ClientSession() as session:
async with session.post(
AUTH_URL, headers={"X-AUTH-TOKEN": API_KEY}
) as resp:
data = await resp.json()
return data["token"]
async def main():
client = Client(WS_URL, token=await get_token())
async def on_publication(event: PublicationEvent):
data = event.data
event_type = data.get("event")
guid = data.get("guid")
if event_type == "job.finished":
print(f"Export {guid} completed!")
elif event_type == "job.progress":
print(f"Export {guid}: {data.get('current')}/{data.get('total')}")
elif event_type == "job.failed":
print(f"Export {guid} failed")
client.on_publication = on_publication
await client.connect()
# Keep the connection alive
try:
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
await client.disconnect()
asyncio.run(main())
```
## Browser (HTML)
Include the SDK via CDN:
```html
```
## Connection Options
| Option | Default | Description |
|--------|---------|-------------|
| `getToken` | — | Async function returning a JWT token string |
| `ping` | `true` | Enable client-side ping to detect stale connections |
| `pingInterval` | `25000` | Ping interval in milliseconds |
| `minReconnectDelay` | `500` | Minimum delay before reconnect attempt (ms) |
| `maxReconnectDelay` | `20000` | Maximum delay before reconnect attempt (ms) |
## Connection Lifecycle
```
disconnected → connecting → connected
↑ ↓
└── disconnected (auto-reconnect)
```
The SDK automatically reconnects with exponential backoff when the connection drops. Token refresh is handled by calling your `getToken` function again.
> **Keep your API key server-side:**
In browser applications, do not embed your API key directly in client-side code. Instead, create a backend endpoint that proxies the token request and returns only the JWT token to the browser.
---
# Event Reference
All events delivered on the `exports:{uuid}` channel follow the standard event envelope. Events on notification and dashboard channels use a typed message format.
## Export Job Events
These events are delivered on the `exports:{uuid}` channel and `export.{guid}.job` channel.
### job.created
Fired when a new export job is created and enters the processing queue.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.created",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": null,
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
}
```
### job.progress
Fired periodically as the export processes items. Throttled to at most one event every 5 seconds per job.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.progress",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "progress",
"url": null,
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "progress",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"totalExported": 678,
"total": 1500
},
"current": 678,
"total": 1500
}
```
### job.finished
Fired when the export completes successfully. The `url` field contains the download link.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.finished",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://exportcomments.com/download/export-b4219d47.xlsx",
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"totalExported": 1500,
"total": 1500,
"download_link": "https://exportcomments.com/download/export-b4219d47.xlsx",
"json_url": "https://exportcomments.com/download/export-b4219d47.json"
}
}
```
### job.failed
Fired when the export encounters an unrecoverable error.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.failed",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "error",
"url": null,
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "error",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"error": "Rate limited by platform"
}
}
```
### job.requeued
Fired when a failed export is requeued for another attempt.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.requeued",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": null,
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
}
```
### job.convert.done
Fired when file format conversion (e.g., JSON to XLSX) completes.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.convert.done",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://exportcomments.com/download/export-b4219d47.xlsx",
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done"
}
}
```
### job.convert.failed
Fired when file format conversion fails.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "job.convert.failed",
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "error",
"url": null,
"details": {
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "error"
}
}
```
## Webhook Events
Delivered on the `webhooks:{uuid}` channel.
### webhook.event.created
Fired when a webhook delivery is attempted.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "webhook.event.created",
"guid": "a1b2c3d4-5678-9abc-def0-123456789abc",
"status": "200",
"url": null,
"details": {
"uuid": "a1b2c3d4-5678-9abc-def0-123456789abc",
"event": "export.finished",
"responseCode": "200",
"webhookUrl": "https://yourapp.com/webhook"
}
}
```
## Account Events
Delivered on the `user:{uuid}` channel.
### subscription.updated
Fired when your subscription plan changes.
**Payload (HTTP 200):**
```json
{
"type": "subscription.updated",
"plan": "business",
"action": "created",
"timestamp": 1705312200
}
```
The `action` field is one of: `created`, `expired`, `canceled`.
## Bulk Download Events
Delivered on the `exports:{uuid}` channel.
### bulk_download.ready
Fired when a bulk download ZIP file is assembled and ready.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "bulk_download.ready",
"guid": "d4e5f6a7-8901-bcde-f234-567890abcdef",
"status": "ready",
"url": "https://exportcomments.com/download/bulk-d4e5f6a7.zip"
}
```
### bulk_download.failed
Fired when a bulk download ZIP assembly fails.
**Payload (HTTP 200):**
```json
{
"version": "v3-beta",
"event": "bulk_download.failed",
"guid": "d4e5f6a7-8901-bcde-f234-567890abcdef",
"status": "error",
"url": null
}
```
## Event Envelope Fields
All events on export channels include these fields:
| Field | Type | Description |
|-------|------|-------------|
| `version` | string | API version (`v3-beta`) |
| `event` | string | Event type identifier |
| `guid` | string | Export job or resource UUID |
| `status` | string | Current status of the resource |
| `url` | string\|null | Download URL (populated when files are ready) |
| `details` | object\|null | Full serialized entity data |
Progress events include additional fields:
| Field | Type | Description |
|-------|------|-------------|
| `current` | integer | Number of items exported so far |
| `total` | integer | Total number of items to export |
---
# Webhooks
Receive real-time notifications when export jobs are created, completed, or fail via HTTPS webhooks with HMAC-SHA256 signature verification.
[premium] [business]
## The Webhook Object
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Unique webhook identifier |
| `url` | string | HTTPS endpoint URL |
| `events` | array | Event types to receive |
| `status` | integer | `0`=disabled, `1`=enabled, `2`=suspended |
| `enabled` | boolean | Whether webhook is active |
| `isSuspended` | boolean | Auto-suspended after 30 consecutive delivery failures |
| `lastEventAt` | datetime | Last event delivery time |
| `webhookEventsCount` | integer | Total events delivered |
| `createdAt` | datetime | When the webhook was created |
## Event Types
| Event | Triggered When |
|-------|---------------|
| `export.created` | A new export job starts processing |
| `export.finished` | An export completes successfully |
| `export.failed` | An export fails |
| `export.requeued` | An export is requeued for retry |
## Suspended Status
A webhook is automatically suspended (`status=2`) after 30 consecutive delivery failures. While suspended, no further events are delivered to that endpoint.
> **Reactivating a suspended webhook:**
To reactivate a suspended webhook, first fix the issue with your endpoint, then use the toggle endpoint to re-enable it. The failure counter resets once the webhook is successfully re-enabled.
## Limits
| Feature | Premium | Business |
|---------|---------|----------|
| Max webhooks | 1 | 10 |
---
# Create a Webhook
POST /api/v1/webhooks
Create a new webhook endpoint to receive real-time export notifications.
## Request Body
- **url** (string, required=true): HTTPS URL for webhook delivery
- **events** (array, required=true): Event types: export.created, export.finished, export.failed, export.requeued
- **signingSecret** (string, required=true): Secret for HMAC-SHA256 signature verification
- **enabled** (boolean, required=false): Enable the webhook (default: true)
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/webhooks \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.finished", "export.failed"],
"signingSecret": "whsec_your_secret_key_here"
}'
```
**python:**
```
response = requests.post(
"https://exportcomments.com/api/v1/webhooks",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.finished", "export.failed"],
"signingSecret": "whsec_your_secret_key_here"
}
)
webhook = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/webhooks', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://your-app.com/webhooks/exportcomments',
events: ['export.finished', 'export.failed'],
signingSecret: 'whsec_your_secret_key_here',
}),
});
const webhook = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/webhooks');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://your-app.com/webhooks/exportcomments',
'events' => ['export.finished', 'export.failed'],
'signingSecret' => 'whsec_your_secret_key_here',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$webhook = json_decode(curl_exec($ch), true);
```
**Response (HTTP 201):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.finished", "export.failed"],
"status": 1,
"enabled": true,
"isSuspended": false,
"lastEventAt": null,
"webhookEventsCount": 0,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# Retrieve a Webhook
GET /api/v1/webhooks/{uuid}
Retrieve details of a specific webhook.
## Path Parameters
- **uuid** (string, required=true): Webhook identifier
**curl:**
```
curl https://exportcomments.com/api/v1/webhooks/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.get(
f"https://exportcomments.com/api/v1/webhooks/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/webhooks/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/webhooks/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$webhook = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.finished", "export.failed"],
"status": 1,
"enabled": true,
"isSuspended": false,
"lastEventAt": "2026-02-22T15:30:00+00:00",
"webhookEventsCount": 42,
"createdAt": "2026-01-15T12:00:00+00:00"
}
```
---
# List Webhooks
GET /api/v1/webhooks
Retrieve all webhook configurations for the authenticated user.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
- **query** (string, required=false): Search webhooks by URL
**curl:**
```
curl https://exportcomments.com/api/v1/webhooks \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/webhooks",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
webhooks = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/webhooks', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const webhooks = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/webhooks');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$webhooks = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.finished", "export.failed"],
"status": 1,
"enabled": true,
"isSuspended": false,
"webhookEventsCount": 42
}
]
```
---
# Update a Webhook
PATCH /api/v1/webhooks/{uuid}
Update an existing webhook configuration. All fields are optional.
## Path Parameters
- **uuid** (string, required=true): Webhook identifier
## Request Body
- **url** (string, required=false): New HTTPS endpoint URL
- **events** (array, required=false): Updated event types
- **signingSecret** (string, required=false): New signing secret
- **enabled** (boolean, required=false): Enable or disable
**curl:**
```
curl -X PATCH https://exportcomments.com/api/v1/webhooks/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{"events": ["export.created", "export.finished", "export.failed"]}'
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.patch(
f"https://exportcomments.com/api/v1/webhooks/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={"events": ["export.created", "export.finished", "export.failed"]}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/webhooks/${uuid}`,
{
method: 'PATCH',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
events: ['export.created', 'export.finished', 'export.failed'],
}),
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/webhooks/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'events' => ['export.created', 'export.finished', 'export.failed'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$webhook = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://your-app.com/webhooks/exportcomments",
"events": ["export.created", "export.finished", "export.failed"],
"status": 1,
"enabled": true
}
```
---
# Delete a Webhook
DELETE /api/v1/webhooks/{uuid}
Permanently delete a webhook and all associated event history.
## Path Parameters
- **uuid** (string, required=true): Webhook identifier
**curl:**
```
curl -X DELETE https://exportcomments.com/api/v1/webhooks/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.delete(
f"https://exportcomments.com/api/v1/webhooks/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
await fetch(`https://exportcomments.com/api/v1/webhooks/${uuid}`, {
method: 'DELETE',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/webhooks/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
```
Returns `204 No Content` on success.
> **Cascading delete:**
Deleting a webhook also removes all associated event history. Use the toggle endpoint to temporarily disable deliveries without losing history.
---
# Toggle Webhook
PATCH /api/v1/webhooks/{uuid}/toggle
Toggle the webhook status between enabled and disabled.
## Path Parameters
- **uuid** (string, required=true): Webhook identifier
**curl:**
```
curl -X PATCH https://exportcomments.com/api/v1/webhooks/a1b2c3d4-5678-90ab-cdef-1234567890ab/toggle \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.patch(
f"https://exportcomments.com/api/v1/webhooks/{uuid}/toggle",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/webhooks/${uuid}/toggle`,
{
method: 'PATCH',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/webhooks/{$uuid}/toggle");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$webhook = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"enabled": false,
"status": 0
}
```
---
# Webhook Delivery Summary
GET /api/v1/webhooks/summary
Retrieve aggregated webhook delivery statistics for monitoring and debugging.
**curl:**
```
curl https://exportcomments.com/api/v1/webhooks/summary \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/webhooks/summary",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
summary = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/webhooks/summary', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const summary = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/webhooks/summary');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$summary = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"total_webhooks": 2,
"active_webhooks": 1,
"total_events": 156,
"successful_deliveries": 148,
"failed_deliveries": 8
}
```
---
# List Webhook Events
GET /api/v1/webhooks/events
Retrieve webhook delivery events for monitoring and debugging.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
- **webhook.uuid** (string, required=false): Filter by webhook UUID
- **event** (string, required=false): Filter by event type
- **message_id** (string, required=false): Filter by message ID
**curl:**
```
curl "https://exportcomments.com/api/v1/webhooks/events?webhook.uuid=a1b2c3d4-5678-90ab-cdef-1234567890ab" \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/webhooks/events",
headers={"X-AUTH-TOKEN": "your-api-key"},
params={"webhook.uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab"}
)
events = response.json()
```
**node:**
```
const response = await fetch(
'https://exportcomments.com/api/v1/webhooks/events?webhook.uuid=a1b2c3d4-5678-90ab-cdef-1234567890ab',
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const events = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/webhooks/events?webhook.uuid=a1b2c3d4-5678-90ab-cdef-1234567890ab');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$events = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "e1e2e3e4-5678-90ab-cdef-1234567890ab",
"event": "export.finished",
"messageId": "msg_abc123",
"responseCode": 200,
"numberOfCalls": 1,
"createdAt": "2026-02-22T15:30:00+00:00"
}
]
```
---
# Resend a Webhook Event
POST /api/v1/webhooks/events/{uuid}/resend
Manually resend a previously delivered webhook event. Use this to retry failed deliveries.
## Path Parameters
- **uuid** (string, required=true): Webhook event identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/webhooks/events/e1e2e3e4-5678-90ab-cdef-1234567890ab/resend \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "e1e2e3e4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/webhooks/events/{uuid}/resend",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'e1e2e3e4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/webhooks/events/${uuid}/resend`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'e1e2e3e4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/webhooks/events/{$uuid}/resend");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"message": "Webhook event queued for redelivery"
}
```
> **Rate limited:**
This endpoint is rate-limited to prevent abuse. The webhook must be enabled and have a valid signing secret.
---
# Webhook Security
Every webhook delivery includes an `X-Signature` header containing an HMAC-SHA256 hash of the request body, signed with your webhook's signing secret.
## Verification Steps
1. Extract the `X-Signature` header from the incoming request
2. Compute the HMAC-SHA256 hash of the raw request body using your signing secret
3. Compare the computed hash with the received signature using a timing-safe comparison
## Code Examples
**python:**
```
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your webhook handler:
# body = request.get_data()
# signature = request.headers.get('X-Signature')
# is_valid = verify_webhook(body, signature, 'whsec_your_secret')
```
**node:**
```
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// In your webhook handler:
// const isValid = verifyWebhook(req.body, req.headers['x-signature'], 'whsec_your_secret');
```
**php:**
```
function verifyWebhook(string $body, string $signature, string $secret): bool
{
$expected = hash_hmac('sha256', $body, $secret);
return hash_equals($expected, $signature);
}
// In your webhook handler:
// $body = file_get_contents('php://input');
// $signature = $_SERVER['HTTP_X_SIGNATURE'];
// $isValid = verifyWebhook($body, $signature, 'whsec_your_secret');
```
**curl:**
```
# Example: compute signature for testing
echo -n '{"event":"export.finished","status":"done"}' | \
openssl dgst -sha256 -hmac "whsec_your_secret"
```
## Event Payload Examples
### export.created
```json
{
"event": "export.created",
"status": "processing",
"platform": "youtube",
"message_id": "msg_abc123"
}
```
### export.finished
```json
{
"event": "export.finished",
"status": "done",
"comments_count": 150,
"download_url": "https://exportcomments.com/exports/12345.xlsx"
}
```
### export.failed
```json
{
"event": "export.failed",
"status": "failed",
"error_message": "Video not found or is private"
}
```
## Best Practices
1. **Always verify signatures** using `hash_equals()` or timing-safe comparison to prevent timing attacks
2. **Use `message_id` for idempotency** to avoid processing duplicate deliveries
3. **Return 200 immediately** and process the webhook payload asynchronously
4. **Auto-suspension**: Webhooks are suspended after 30 consecutive delivery failures. Re-enable via the toggle endpoint after fixing your endpoint
---
# Workflows
Build multi-step export pipelines that chain actions together and run on a schedule. [premium] [business]
## The Workflow Object
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Unique workflow identifier |
| `url` | string | Source URL to monitor |
| `platform` | string | Auto-detected platform |
| `name` | string | Display name |
| `frequency` | string | hourly, daily, weekly, monthly, or custom |
| `customCron` | string | Custom cron expression (Business only) |
| `preferredTime` | time | Preferred run time (HH:MM) |
| `timezone` | string | Timezone (default: UTC) |
| `steps` | array | Pipeline steps (see below) |
| `enabled` | boolean | Whether workflow is active |
| `status` | string | active, paused, failed, subscription_expired, tier_invalid |
| `deliveryMethod` | string | storage, email, or webhook |
| `sendNotification` | boolean | Send email notification on completion |
| `lastRunAt` | datetime | Last execution time |
| `nextRunAt` | datetime | Next scheduled execution |
| `runCount` | integer | Total successful runs |
| `failCount` | integer | Total failed runs |
| `createdAt` | datetime | Creation time |
## The Step Object
Each workflow contains an ordered array of steps that execute sequentially. Steps can be chained so that the output of one step feeds into the next.
| Field | Type | Description |
|-------|------|-------------|
| `position` | integer | Step order (1-based) |
| `action` | string | Action to perform (see supported actions below) |
| `inputMode` | string | `url` (step 1) or `previous_results` (chained steps) |
| `options` | object | Export options (limit, cookies, etc.) |
| `conditions` | object | Filters: `max_age_hours`, `keyword` |
## Supported Actions
| Action | Description |
|--------|-------------|
| `export_posts` | Export posts from a page or profile |
| `export_comments` | Export comments from a post or page |
| `export_likes` | Export likes/reactions |
| `export_shares` | Export shares/reposts |
| `export_followers` | Export followers list |
| `export_reviews` | Export reviews |
## Platform Actions
Not all actions are available on every platform. Use the [Pipeline Info](/workflows/pipeline-info) endpoint to query available actions for a given URL.
### Social Media
| Platform | Posts | Comments | Likes | Shares | Followers | Reviews |
|----------|:-----:|:--------:|:-----:|:------:|:---------:|:-------:|
| Facebook | Yes | Yes | Yes | Yes | Yes | Yes |
| Instagram | Yes | Yes | Yes | - | Yes | - |
| Twitter/X | Yes | Yes | Yes | - | Yes | - |
| TikTok | Yes | Yes | Yes | - | Yes | - |
| YouTube | Yes | Yes | Yes | - | - | - |
| Reddit | Yes | Yes | - | - | - | - |
| LinkedIn | Yes | Yes | Yes | Yes | - | - |
| Threads | Yes | Yes | Yes | - | - | - |
| VK | Yes | Yes | - | - | - | - |
### Video Platforms
| Platform | Comments |
|----------|:--------:|
| Twitch | Yes |
| Vimeo | Yes |
| Bilibili | Yes |
| RuTube | Yes |
| Kuaishou | Yes |
### Review Platforms
| Platform | Reviews |
|----------|:-------:|
| Trustpilot | Yes |
| Amazon | Yes |
| Google | Yes |
| TripAdvisor | Yes |
| Etsy | Yes |
| Apple | Yes |
| Airbnb | Yes |
| Yelp | Yes |
### E-commerce
| Platform | Reviews |
|----------|:-------:|
| Walmart | Yes |
| Best Buy | Yes |
| Flipkart | Yes |
| AliExpress | Yes |
| Lazada | Yes |
| Shopee | Yes |
| Expedia | Yes |
| IMDb | Yes |
| Chrome Web Store | Yes |
| AppGallery | Yes |
### Community & Content
| Platform | Comments | Reviews |
|----------|:--------:|:-------:|
| Discord | Yes | - |
| Steam | Yes | Yes |
| Product Hunt | Yes | Yes |
| Disqus | Yes | - |
| Change.org | Yes | - |
| Figma | Yes | - |
| Kickstarter | Yes | - |
| Indiegogo | Yes | - |
| Yahoo | Yes | - |
| Newsweek | Yes | - |
## Pipeline Transitions
Steps can be chained using `inputMode: "previous_results"`. Only certain action combinations are valid:
| From Action | Can Chain To |
|-------------|-------------|
| `export_posts` | `export_comments`, `export_likes`, `export_shares` |
| `export_shares` | `export_comments`, `export_likes` |
| `export_comments` | _(terminal — no further chaining)_ |
| `export_likes` | _(terminal)_ |
| `export_followers` | _(terminal)_ |
| `export_reviews` | _(terminal)_ |
### Common Pipelines
- **Comments Monitor** — `export_posts` → `export_comments` — Export new posts, then fetch comments on each
- **Engagement Tracker** — `export_posts` → `export_likes` — Export new posts, then fetch likes/reactions
- **Viral Tracker** — `export_posts` → `export_shares` → `export_comments` — Track shares, then comments on shared posts
- **Review Monitor** — `export_reviews` — Automatically export new reviews on a schedule
- **Channel Monitor** — `export_comments` — Export messages from Discord, Twitch, and other channels
> **Step chaining:**
The first step in a workflow must use `inputMode: "url"`. All subsequent steps should use `inputMode: "previous_results"` to process the output of the preceding step. New items are automatically deduplicated between runs.
## Limits
| Feature | Premium | Business |
|---------|---------|----------|
| Max workflows | 2 | 10 |
| Max steps per workflow | 2 | 5 |
| Max results per step | 10 | 100 |
| Custom cron | No | Yes |
## Frequencies by Tier
| Frequency | Premium | Business |
|-----------|---------|----------|
| `hourly` | No | Yes |
| `daily` | Yes | Yes |
| `weekly` | Yes | Yes |
| `monthly` | Yes | Yes |
| `custom` (cron) | No | Yes |
## Delivery Methods
- `storage` - Save to your account (default)
- `email` - Send email notification when complete
- `webhook` - POST results to your webhook endpoint
> **Webhook delivery:**
When using `webhook` delivery, provide the webhook UUID (not the URL) in the `webhook` field. Create webhooks first using the `/api/v1/webhooks` endpoints.
---
# Create a Workflow
POST /api/v1/workflows
Create a new multi-step export workflow with chained actions.
## Request Body
- **url** (string, required=true): Source URL to monitor
- **name** (string, required=false): Display name for the workflow
- **frequency** (string, required=true): Frequency: hourly, daily, weekly, monthly, or custom
- **customCron** (string, required=false): Cron expression (required if frequency is "custom", Business only)
- **preferredTime** (string, required=false): Preferred time in HH:MM format
- **timezone** (string, required=false): Timezone (default: UTC)
- **steps** (array, required=true): Array of step objects defining the pipeline
- **deliveryMethod** (string, required=false): Delivery: storage, email, or webhook (default: storage)
- **webhook** (string (uuid), required=false): Webhook UUID for webhook delivery
- **sendNotification** (boolean, required=false): Send email notification (default: true)
### Step Object Parameters
- **position** (integer, required=true): Step order (1-based)
- **action** (string, required=true): Action: export_posts, export_comments, export_likes, export_shares, export_followers, export_reviews
- **inputMode** (string, required=true): Input source: "url" for step 1, "previous_results" for chained steps
- **options** (object, required=false): Export options (limit, cookies, etc.)
- **conditions** (object, required=false): Filters: max_age_hours, keyword
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/workflows \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
"name": "YouTube Posts + Comments",
"frequency": "daily",
"preferredTime": "09:00",
"timezone": "America/New_York",
"steps": [
{
"position": 1,
"action": "export_posts",
"inputMode": "url",
"options": {"limit": 50}
},
{
"position": 2,
"action": "export_comments",
"inputMode": "previous_results",
"options": {"limit": 100},
"conditions": {"max_age_hours": 72}
}
],
"deliveryMethod": "storage"
}'
```
**python:**
```
response = requests.post(
"https://exportcomments.com/api/v1/workflows",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
"name": "YouTube Posts + Comments",
"frequency": "daily",
"preferredTime": "09:00",
"timezone": "America/New_York",
"steps": [
{
"position": 1,
"action": "export_posts",
"inputMode": "url",
"options": {"limit": 50}
},
{
"position": 2,
"action": "export_comments",
"inputMode": "previous_results",
"options": {"limit": 100},
"conditions": {"max_age_hours": 72}
}
],
"deliveryMethod": "storage"
}
)
workflow = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/workflows', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw',
name: 'YouTube Posts + Comments',
frequency: 'daily',
preferredTime: '09:00',
timezone: 'America/New_York',
steps: [
{
position: 1,
action: 'export_posts',
inputMode: 'url',
options: { limit: 50 },
},
{
position: 2,
action: 'export_comments',
inputMode: 'previous_results',
options: { limit: 100 },
conditions: { max_age_hours: 72 },
},
],
deliveryMethod: 'storage',
}),
});
const workflow = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/workflows');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw',
'name' => 'YouTube Posts + Comments',
'frequency' => 'daily',
'preferredTime' => '09:00',
'timezone' => 'America/New_York',
'steps' => [
[
'position' => 1,
'action' => 'export_posts',
'inputMode' => 'url',
'options' => ['limit' => 50],
],
[
'position' => 2,
'action' => 'export_comments',
'inputMode' => 'previous_results',
'options' => ['limit' => 100],
'conditions' => ['max_age_hours' => 72],
],
],
'deliveryMethod' => 'storage',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$workflow = json_decode(curl_exec($ch), true);
```
**Response (HTTP 201):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
"platform": "youtube.com",
"name": "YouTube Posts + Comments",
"frequency": "daily",
"customCron": null,
"preferredTime": "09:00:00",
"timezone": "America/New_York",
"steps": [
{
"position": 1,
"action": "export_posts",
"inputMode": "url",
"options": {"limit": 50},
"conditions": null
},
{
"position": 2,
"action": "export_comments",
"inputMode": "previous_results",
"options": {"limit": 100},
"conditions": {"max_age_hours": 72}
}
],
"enabled": true,
"status": "active",
"deliveryMethod": "storage",
"sendNotification": true,
"lastRunAt": null,
"nextRunAt": "2026-03-18T09:00:00-04:00",
"runCount": 0,
"failCount": 0,
"createdAt": "2026-03-17T12:00:00+00:00"
}
```
---
# Retrieve a Workflow
GET /api/v1/workflows/{uuid}
Retrieve details of a specific workflow, including its steps and recent run history.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
**curl:**
```
curl https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.get(
f"https://exportcomments.com/api/v1/workflows/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
workflow = response.json()
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/workflows/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const workflow = await response.json();
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$workflow = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
"platform": "youtube.com",
"name": "YouTube Posts + Comments",
"frequency": "daily",
"status": "active",
"enabled": true,
"steps": [
{
"position": 1,
"action": "export_posts",
"inputMode": "url",
"options": {"limit": 50},
"conditions": null
},
{
"position": 2,
"action": "export_comments",
"inputMode": "previous_results",
"options": {"limit": 100},
"conditions": {"max_age_hours": 72}
}
],
"nextRunAt": "2026-03-18T09:00:00-04:00",
"runCount": 5,
"failCount": 0,
"recent_runs": [
{
"startedAt": "2026-03-17T09:00:00-04:00",
"completedAt": "2026-03-17T09:04:15-04:00",
"status": "completed",
"stepsCompleted": 2,
"totalResults": 1247
},
{
"startedAt": "2026-03-16T09:00:00-04:00",
"completedAt": "2026-03-16T09:03:45-04:00",
"status": "completed",
"stepsCompleted": 2,
"totalResults": 1183
}
]
}
```
---
# List Workflows
GET /api/v1/workflows
Get all workflows for the authenticated user.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
**curl:**
```
curl https://exportcomments.com/api/v1/workflows \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/workflows",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
workflows = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/workflows', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const workflows = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/workflows');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$workflows = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
"name": "YouTube Posts + Comments",
"frequency": "daily",
"status": "active",
"enabled": true,
"steps": [
{"position": 1, "action": "export_posts"},
{"position": 2, "action": "export_comments"}
],
"nextRunAt": "2026-03-18T09:00:00-04:00",
"runCount": 5
}
]
```
---
# Update a Workflow
PUT /api/v1/workflows/{uuid}
Update an existing workflow configuration.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
## Request Body
- **url** (string, required=false): Updated source URL
- **name** (string, required=false): Updated display name
- **frequency** (string, required=false): Updated frequency
- **customCron** (string, required=false): Updated cron expression
- **preferredTime** (string, required=false): Updated preferred time
- **timezone** (string, required=false): Updated timezone
- **steps** (array, required=false): Updated pipeline steps (replaces all steps)
- **deliveryMethod** (string, required=false): Updated delivery method
- **sendNotification** (boolean, required=false): Updated notification setting
**curl:**
```
curl -X PUT https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{"frequency": "weekly", "preferredTime": "10:00"}'
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.put(
f"https://exportcomments.com/api/v1/workflows/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={"frequency": "weekly", "preferredTime": "10:00"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/workflows/${uuid}`,
{
method: 'PUT',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({ frequency: 'weekly', preferredTime: '10:00' }),
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'frequency' => 'weekly',
'preferredTime' => '10:00',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$workflow = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"frequency": "weekly",
"preferredTime": "10:00:00",
"nextRunAt": "2026-03-24T10:00:00-04:00",
"status": "active"
}
```
---
# Delete a Workflow
DELETE /api/v1/workflows/{uuid}
Permanently delete a workflow and all its steps.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
**curl:**
```
curl -X DELETE https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.delete(
f"https://exportcomments.com/api/v1/workflows/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
await fetch(`https://exportcomments.com/api/v1/workflows/${uuid}`, {
method: 'DELETE',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
```
Returns `204 No Content` on success.
---
# Pause a Workflow
POST /api/v1/workflows/{uuid}/pause
Temporarily pause a workflow. The workflow will not execute until resumed.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab/pause \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/workflows/{uuid}/pause",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/workflows/${uuid}/pause`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}/pause");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "paused",
"enabled": false
}
```
---
# Resume a Workflow
POST /api/v1/workflows/{uuid}/resume
Resume a paused workflow. The next run time will be recalculated.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab/resume \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/workflows/{uuid}/resume",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/workflows/${uuid}/resume`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}/resume");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "active",
"enabled": true,
"nextRunAt": "2026-03-18T09:00:00-04:00"
}
```
---
# Run Workflow Now
POST /api/v1/workflows/{uuid}/run-now
Trigger an immediate run of a workflow, regardless of the next scheduled time. All steps execute sequentially.
## Path Parameters
- **uuid** (string, required=true): Workflow identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/workflows/a1b2c3d4-5678-90ab-cdef-1234567890ab/run-now \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "a1b2c3d4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/workflows/{uuid}/run-now",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/workflows/${uuid}/run-now`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'a1b2c3d4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/workflows/{$uuid}/run-now");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"message": "Workflow triggered successfully",
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "active"
}
```
> **Note:**
This triggers an immediate execution of all workflow steps using the configured URL and options. The next scheduled run time is not affected.
---
# Pipeline Info
GET /api/v1/workflows/pipeline-info
Query available actions, valid pipeline transitions, and suggested templates for a given URL. Useful for building dynamic workflow creation UIs.
## Query Parameters
- **url** (string, required=false): URL to detect platform and available actions. If omitted, all actions are returned.
**curl:**
```
curl "https://exportcomments.com/api/v1/workflows/pipeline-info?url=https://www.facebook.com/cocacola" \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/workflows/pipeline-info",
headers={"X-AUTH-TOKEN": "your-api-key"},
params={"url": "https://www.facebook.com/cocacola"}
)
info = response.json()
```
**node:**
```
const response = await fetch(
'https://exportcomments.com/api/v1/workflows/pipeline-info?url=https://www.facebook.com/cocacola',
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const info = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/workflows/pipeline-info?url=' . urlencode('https://www.facebook.com/cocacola'));
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$info = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"platform": "facebook",
"available_actions": [
"export_posts",
"export_comments",
"export_likes",
"export_shares",
"export_followers",
"export_reviews"
],
"transitions": {
"export_posts": ["export_comments", "export_likes", "export_shares"],
"export_comments": [],
"export_likes": [],
"export_shares": ["export_comments", "export_likes"],
"export_followers": [],
"export_reviews": []
},
"templates": [
{
"id": "comments_monitor",
"name": "Comments Monitor",
"description": "Export new posts, then export comments on each",
"steps": [
{"position": 1, "action": "export_posts", "inputMode": "url", "options": {"limit": 50}, "conditions": []},
{"position": 2, "action": "export_comments", "inputMode": "previous_results", "options": {"limit": 5000}, "conditions": []}
]
},
{
"id": "engagement_tracker",
"name": "Engagement Tracker",
"description": "Export new posts, then export likes/reactions",
"steps": [
{"position": 1, "action": "export_posts", "inputMode": "url", "options": {"limit": 50}, "conditions": []},
{"position": 2, "action": "export_likes", "inputMode": "previous_results", "options": {"limit": 5000}, "conditions": []}
]
},
{
"id": "viral_tracker",
"name": "Viral Content Tracker",
"description": "Export shares, then track comments on shared posts",
"steps": [
{"position": 1, "action": "export_posts", "inputMode": "url", "options": {"limit": 50}, "conditions": []},
{"position": 2, "action": "export_shares", "inputMode": "previous_results", "options": {"limit": 1000}, "conditions": []}
]
},
{
"id": "review_monitor",
"name": "Review Monitor",
"description": "Automatically export new reviews on a schedule",
"steps": [
{"position": 1, "action": "export_reviews", "inputMode": "url", "options": {"limit": 5000}, "conditions": []}
]
}
],
"platform_actions": {
"facebook": ["export_posts", "export_comments", "export_likes", "export_shares", "export_followers", "export_reviews"],
"instagram": ["export_posts", "export_comments", "export_likes", "export_followers"],
"youtube": ["export_posts", "export_comments", "export_likes"],
"..."
},
"platform_categories": {
"social": ["facebook", "instagram", "twitter", "youtube", "tiktok", "reddit", "linkedin", "thread", "vk"],
"video": ["youtube", "tiktok", "twitch", "vimeo", "bilibili", "rutube", "kuaishou"],
"reviews": ["trustpilot", "amazon", "google", "tripadvisor", "etsy", "steam", "apple", "airbnb", "yelp", "walmart", "bestbuy", "flipkart", "aliexpress", "lazada", "shopee", "expedia", "imdb", "chromestore", "appgallery"],
"community": ["discord", "disqus", "change", "figma", "kickstarter", "indiegogo", "producthunt", "yahoo", "newsweek"]
}
}
```
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `platform` | string\|null | Detected platform from the URL |
| `available_actions` | array | Actions supported by the detected platform |
| `transitions` | object | Valid pipeline transitions (action → valid next actions) |
| `templates` | array | Suggested pipeline templates for this platform |
| `platform_actions` | object | Full mapping of all platforms to their supported actions |
| `platform_categories` | object | Platforms grouped by category (social, video, reviews, community) |
> **Dynamic UI:**
Use this endpoint to build dynamic workflow creation forms. The `available_actions` field tells you which actions to show in dropdowns, `transitions` determines valid step chaining, and `templates` provides one-click pipeline presets.
---
# Scheduled Exports
Schedule recurring exports to run automatically at specified intervals. [premium] [business]
## The Schedule Object
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Unique schedule identifier |
| `url` | string | URL to export |
| `platform` | string | Detected platform |
| `name` | string | Display name |
| `frequency` | string | hourly, daily, weekly, monthly, or custom |
| `customCron` | string | Custom cron expression (Business only) |
| `preferredTime` | time | Preferred run time |
| `timezone` | string | Timezone (default: UTC) |
| `exportOptions` | object | Export options applied to each run |
| `enabled` | boolean | Whether schedule is active |
| `status` | string | active, paused, failed, subscription_expired, tier_invalid |
| `deliveryMethod` | string | storage, email, or webhook |
| `webhook` | object | Associated webhook (for webhook delivery) |
| `sendNotification` | boolean | Send email notification on completion |
| `lastRunAt` | datetime | Last execution time |
| `nextRunAt` | datetime | Next scheduled execution |
| `runCount` | integer | Total successful runs |
| `failCount` | integer | Total failed runs |
| `createdAt` | datetime | Creation time |
| `updatedAt` | datetime | Last update time |
## Frequencies by Tier
| Frequency | Premium | Business |
|-----------|---------|----------|
| `hourly` | No | Yes |
| `daily` | Yes | Yes |
| `weekly` | Yes | Yes |
| `monthly` | Yes | Yes |
| `custom` (cron) | No | Yes |
## Limits
| Feature | Premium | Business |
|---------|---------|----------|
| Max schedules | 3 | 20 |
| Webhook delivery | Yes | Yes |
| Custom cron | No | Yes |
## Delivery Methods
- `storage` - Save to your account (default)
- `email` - Send email notification when complete
- `webhook` - POST results to your webhook endpoint
> **Webhook delivery:**
When using `webhook` delivery, provide the webhook UUID (not the URL) in the `webhook` field. Create webhooks first using the `/api/v1/webhooks` endpoints.
---
# Create a Scheduled Export
POST /api/v1/schedules
Create a new recurring export schedule.
## Request Body
- **url** (string, required=true): URL to export
- **name** (string, required=false): Display name for the schedule
- **frequency** (string, required=true): Frequency: hourly, daily, weekly, monthly, or custom
- **customCron** (string, required=false): Cron expression (required if frequency is "custom", Business only)
- **preferredTime** (string, required=false): Preferred time in HH:MM format
- **timezone** (string, required=false): Timezone (default: UTC)
- **exportOptions** (object, required=false): Export options (same as job creation options)
- **deliveryMethod** (string, required=false): Delivery: storage, email, or webhook (default: storage)
- **webhook** (string (uuid), required=false): Webhook UUID for webhook delivery
- **sendNotification** (boolean, required=false): Send email notification (default: true)
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/schedules \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"name": "Daily YouTube Export",
"frequency": "daily",
"preferredTime": "09:00",
"timezone": "America/New_York",
"exportOptions": {"limit": 1000, "replies": true},
"deliveryMethod": "storage"
}'
```
**python:**
```
response = requests.post(
"https://exportcomments.com/api/v1/schedules",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"name": "Daily YouTube Export",
"frequency": "daily",
"preferredTime": "09:00",
"timezone": "America/New_York",
"exportOptions": {"limit": 1000, "replies": True},
"deliveryMethod": "storage"
}
)
schedule = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/schedules', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
name: 'Daily YouTube Export',
frequency: 'daily',
preferredTime: '09:00',
timezone: 'America/New_York',
exportOptions: { limit: 1000, replies: true },
deliveryMethod: 'storage',
}),
});
const schedule = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/schedules');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'name' => 'Daily YouTube Export',
'frequency' => 'daily',
'preferredTime' => '09:00',
'timezone' => 'America/New_York',
'exportOptions' => ['limit' => 1000, 'replies' => true],
'deliveryMethod' => 'storage',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$schedule = json_decode(curl_exec($ch), true);
```
**Response (HTTP 201):**
```json
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"platform": "youtube.com",
"name": "Daily YouTube Export",
"frequency": "daily",
"customCron": null,
"preferredTime": "09:00:00",
"timezone": "America/New_York",
"exportOptions": {"limit": 1000, "replies": true},
"enabled": true,
"status": "active",
"deliveryMethod": "storage",
"sendNotification": true,
"lastRunAt": null,
"nextRunAt": "2026-02-24T09:00:00-05:00",
"runCount": 0,
"failCount": 0,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# Retrieve a Scheduled Export
GET /api/v1/schedules/{uuid}
Retrieve details of a specific scheduled export, including recent run history.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
**curl:**
```
curl https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.get(
f"https://exportcomments.com/api/v1/schedules/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
schedule = response.json()
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/schedules/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const schedule = await response.json();
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$schedule = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"platform": "youtube.com",
"name": "Daily YouTube Export",
"frequency": "daily",
"status": "active",
"enabled": true,
"nextRunAt": "2026-02-24T09:00:00-05:00",
"runCount": 5,
"failCount": 0,
"recent_history": [
{
"startedAt": "2026-02-23T09:00:00-05:00",
"completedAt": "2026-02-23T09:02:30-05:00",
"status": "completed",
"commentsExported": 847
}
]
}
```
---
# List Scheduled Exports
GET /api/v1/schedules
Get all scheduled exports for the authenticated user.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
**curl:**
```
curl https://exportcomments.com/api/v1/schedules \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
response = requests.get(
"https://exportcomments.com/api/v1/schedules",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
schedules = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/schedules', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const schedules = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/schedules');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$schedules = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"name": "Daily YouTube Export",
"frequency": "daily",
"status": "active",
"enabled": true,
"nextRunAt": "2026-02-24T09:00:00-05:00",
"runCount": 5
}
]
```
---
# Update a Scheduled Export
PUT /api/v1/schedules/{uuid}
Update an existing scheduled export configuration.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
## Request Body
- **url** (string, required=false): Updated URL
- **name** (string, required=false): Updated display name
- **frequency** (string, required=false): Updated frequency
- **customCron** (string, required=false): Updated cron expression
- **preferredTime** (string, required=false): Updated preferred time
- **timezone** (string, required=false): Updated timezone
- **exportOptions** (object, required=false): Updated export options
- **deliveryMethod** (string, required=false): Updated delivery method
- **sendNotification** (boolean, required=false): Updated notification setting
**curl:**
```
curl -X PUT https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{"frequency": "weekly", "preferredTime": "10:00"}'
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.put(
f"https://exportcomments.com/api/v1/schedules/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={"frequency": "weekly", "preferredTime": "10:00"}
)
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/schedules/${uuid}`,
{
method: 'PUT',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({ frequency: 'weekly', preferredTime: '10:00' }),
}
);
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'frequency' => 'weekly',
'preferredTime' => '10:00',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$schedule = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"frequency": "weekly",
"preferredTime": "10:00:00",
"nextRunAt": "2026-03-02T10:00:00-05:00",
"status": "active"
}
```
---
# Delete a Scheduled Export
DELETE /api/v1/schedules/{uuid}
Permanently delete a scheduled export.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
**curl:**
```
curl -X DELETE https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.delete(
f"https://exportcomments.com/api/v1/schedules/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
await fetch(`https://exportcomments.com/api/v1/schedules/${uuid}`, {
method: 'DELETE',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
```
Returns `204 No Content` on success.
---
# Pause a Scheduled Export
POST /api/v1/schedules/{uuid}/pause
Temporarily pause a scheduled export. The schedule will not execute until resumed.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab/pause \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/schedules/{uuid}/pause",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/schedules/${uuid}/pause`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}/pause");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"status": "paused",
"enabled": false
}
```
---
# Resume a Scheduled Export
POST /api/v1/schedules/{uuid}/resume
Resume a paused scheduled export. The next run time will be recalculated.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab/resume \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/schedules/{uuid}/resume",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/schedules/${uuid}/resume`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}/resume");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"status": "active",
"enabled": true,
"nextRunAt": "2026-02-24T09:00:00-05:00"
}
```
---
# Run Scheduled Export Now
POST /api/v1/schedules/{uuid}/run-now
Trigger an immediate run of a scheduled export, regardless of the next scheduled time.
## Path Parameters
- **uuid** (string, required=true): Schedule identifier
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/schedules/c1d2e3f4-5678-90ab-cdef-1234567890ab/run-now \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
uuid = "c1d2e3f4-5678-90ab-cdef-1234567890ab"
response = requests.post(
f"https://exportcomments.com/api/v1/schedules/{uuid}/run-now",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
```
**node:**
```
const uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/schedules/${uuid}/run-now`,
{
method: 'POST',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
}
);
```
**php:**
```
$uuid = 'c1d2e3f4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/schedules/{$uuid}/run-now");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"message": "Export triggered successfully",
"uuid": "c1d2e3f4-5678-90ab-cdef-1234567890ab",
"status": "active"
}
```
> **Note:**
This triggers an immediate export using the schedule's configured URL and options. The next scheduled run time is not affected.
---
# Proxies
Use custom proxy pools to route your exports through specific proxy servers. [business]
## The Proxy Pool Object
| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Unique pool identifier |
| `name` | string | Display name for the pool |
| `proxies` | array/string | Proxy entries |
| `proxyCount` | integer | Number of proxies in the pool |
| `createdAt` | datetime | Creation time |
## Proxy Format
Proxies can be provided as a CSV string with each proxy on a new line:
```
protocol://address:port;username;password
```
Example:
```
http://192.168.1.1:8080;user;pass
https://proxy2.com:3128;;
socks5://10.0.0.1:1080;admin;secret
```
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v1/proxies` | Create a new proxy pool |
| `GET` | `/api/v1/proxies/{uuid}` | Retrieve a specific proxy pool |
| `GET` | `/api/v1/proxies` | List all proxy pools |
| `PATCH` | `/api/v1/proxies/{uuid}` | Update a proxy pool |
| `DELETE` | `/api/v1/proxies/{uuid}` | Delete a proxy pool |
## Using a Proxy Pool
Once you have created a proxy pool, pass its `uuid` in the `options.pool` field when creating an export job:
```json
{
"url": "https://www.instagram.com/p/example/",
"options": {
"pool": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
> **Business tier required:**
Custom proxy pools are available on the Business tier. On lower tiers, the `pool` option is ignored and exports use the default shared proxy infrastructure.
---
# Create a Proxy Pool
POST /api/v1/proxies
Create a new proxy pool with custom proxies.
## Request Body
- **name** (string, required=true): Display name for the proxy pool
- **proxies** (string, required=true): Proxies in CSV format (one per line: protocol://address:port;username;password)
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/proxies \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "My Residential Proxies",
"proxies": "http://192.168.1.1:8080;user;pass\nhttps://proxy2.com:3128;;"
}'
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/v1/proxies",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"name": "My Residential Proxies",
"proxies": "http://192.168.1.1:8080;user;pass\nhttps://proxy2.com:3128;;"
}
)
pool = response.json()
print(f"Pool created: {pool['uuid']}")
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/proxies', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'My Residential Proxies',
proxies: 'http://192.168.1.1:8080;user;pass\nhttps://proxy2.com:3128;;',
}),
});
const pool = await response.json();
console.log(`Pool created: ${pool.uuid}`);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/proxies');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'My Residential Proxies',
'proxies' => "http://192.168.1.1:8080;user;pass\nhttps://proxy2.com:3128;;",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$pool = json_decode(curl_exec($ch), true);
echo "Pool created: " . $pool['uuid'];
```
**Response (HTTP 201):**
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Residential Proxies",
"proxyCount": 2,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# Retrieve a Proxy Pool
GET /api/v1/proxies/{uuid}
Retrieve details of a specific proxy pool.
## Path Parameters
- **uuid** (string, required=true): Proxy pool identifier
**curl:**
```
curl https://exportcomments.com/api/v1/proxies/550e8400-e29b-41d4-a716-446655440000 \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.get(
f"https://exportcomments.com/api/v1/proxies/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
pool = response.json()
```
**node:**
```
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://exportcomments.com/api/v1/proxies/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const pool = await response.json();
```
**php:**
```
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$ch = curl_init("https://exportcomments.com/api/v1/proxies/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$pool = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Residential Proxies",
"proxyCount": 2,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# List Proxy Pools
GET /api/v1/proxies
Retrieve all proxy pools for the authenticated user.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
**curl:**
```
curl https://exportcomments.com/api/v1/proxies \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.get(
"https://exportcomments.com/api/v1/proxies",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
pools = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/proxies', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const pools = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/proxies');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$pools = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Residential Proxies",
"proxyCount": 2,
"createdAt": "2026-02-23T12:00:00+00:00"
}
]
```
---
# Update a Proxy Pool
PATCH /api/v1/proxies/{uuid}
Update proxy pool name or replace proxies. Only the fields you provide are updated.
## Path Parameters
- **uuid** (string, required=true): Proxy pool identifier
## Request Body
- **name** (string, required=false): Updated pool name
- **proxies** (string, required=false): Updated proxies in CSV format (replaces existing proxies)
**curl:**
```
curl -X PATCH https://exportcomments.com/api/v1/proxies/550e8400-e29b-41d4-a716-446655440000 \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Pool Name"}'
```
**python:**
```
import requests
uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.patch(
f"https://exportcomments.com/api/v1/proxies/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={"name": "Updated Pool Name"}
)
pool = response.json()
```
**node:**
```
const uuid = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://exportcomments.com/api/v1/proxies/${uuid}`,
{
method: 'PATCH',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Updated Pool Name' }),
}
);
const pool = await response.json();
```
**php:**
```
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$ch = curl_init("https://exportcomments.com/api/v1/proxies/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['name' => 'Updated Pool Name']),
CURLOPT_RETURNTRANSFER => true,
]);
$pool = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Updated Pool Name",
"proxyCount": 2,
"createdAt": "2026-02-23T12:00:00+00:00"
}
```
---
# Delete a Proxy Pool
DELETE /api/v1/proxies/{uuid}
Permanently delete a proxy pool. Jobs using this pool will fall back to default proxies.
## Path Parameters
- **uuid** (string, required=true): Proxy pool identifier
**curl:**
```
curl -X DELETE https://exportcomments.com/api/v1/proxies/550e8400-e29b-41d4-a716-446655440000 \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.delete(
f"https://exportcomments.com/api/v1/proxies/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
# 204 No Content on success
```
**node:**
```
const uuid = '550e8400-e29b-41d4-a716-446655440000';
await fetch(`https://exportcomments.com/api/v1/proxies/${uuid}`, {
method: 'DELETE',
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
// 204 No Content on success
```
**php:**
```
$uuid = '550e8400-e29b-41d4-a716-446655440000';
$ch = curl_init("https://exportcomments.com/api/v1/proxies/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
// 204 No Content on success
```
Returns `204 No Content` on success.
---
# CLI & MCP Server
A command-line tool and [MCP server](https://modelcontextprotocol.io) for the ExportComments API. Export comments and reviews from 40+ platforms directly from your terminal or through AI assistants like Claude, Cursor, and Windsurf.
## Installation
Install globally via npm:
```bash
npm install -g exportcomments-cli
```
Or run directly with npx:
```bash
npx exportcomments-cli export https://www.instagram.com/p/ABC123/
```
> **Node.js requirement:**
The CLI requires Node.js 18 or later.
## Authentication
Get your API token from the [API dashboard](https://app.exportcomments.com/user/api), then set it as an environment variable:
```bash
export EXPORTCOMMENTS_API_TOKEN="your-token-here"
```
Or pass it with each command:
```bash
exportcomments --token "your-token-here" export https://...
```
## Commands
| Command | Alias | Description |
|---------|-------|-------------|
| `export` | `create` | Create a new export job |
| `status` | `check` | Check export job status |
| `list` | `ls` | List all export jobs |
| `download` | `dl` | Download completed export |
| `platforms` | — | List or detect supported platforms |
| `ping` | — | Check API connectivity |
## Output Format
All commands output structured JSON for easy parsing and automation:
```json
{
"ok": true,
"data": { ... }
}
```
On error:
```json
{
"ok": false,
"error": "Human-readable error message",
"error_code": "MACHINE_READABLE_CODE",
"detail": "Additional context"
}
```
## Job Statuses
| Status | Description |
|--------|-------------|
| `queueing` | Job is queued for processing |
| `progress` | Job is being processed |
| `done` | Job completed successfully |
| `error` | Job failed with an error |
## Quick Example
```bash
# 1. Detect the platform
exportcomments platforms --detect "https://www.instagram.com/p/ABC123/"
# 2. Create an export and wait for completion
exportcomments export "https://www.instagram.com/p/ABC123/" --replies --wait
# 3. Download the raw data
exportcomments download --json
```
---
# export
Create a new export job for a URL. Alias: `create`.
```bash
exportcomments export [options]
```
## Options
- **--replies** (flag, required=false): Include replies to comments
- **--limit ** (number, required=false): Maximum number of items to export
- **--min-date ** (string, required=false): Start date filter (ISO 8601)
- **--max-date ** (string, required=false): End date filter (ISO 8601)
- **--vpn ** (string, required=false): Use VPN with the specified country code
- **--cookies ** (string, required=false): Cookies as a JSON string
- **--tweets** (flag, required=false): Include tweets (Twitter/X only)
- **--followers** (flag, required=false): Export followers (Twitter/X only)
- **--following** (flag, required=false): Export following list (Twitter/X only)
- **--likes** (flag, required=false): Export likes
- **--shares** (flag, required=false): Include shares data
- **--advanced** (flag, required=false): Enable advanced export features
- **--facebook-ads** (flag, required=false): Include Facebook ads data
- **--wait** (flag, required=false): Wait for the job to complete (polls every 5s)
- **--realtime** (flag, required=false): Use WebSocket for real-time status updates
- **--wait-interval ** (number, required=false): Custom polling interval in milliseconds
- **--wait-timeout ** (number, required=false): Custom timeout in milliseconds (default: 600000)
- **--download** (flag, required=false): Download Excel/CSV file when done (implies --wait)
- **--download-json** (flag, required=false): Download raw JSON when done (implies --wait)
## Examples
Basic export:
```bash
exportcomments export https://www.instagram.com/p/ABC123/
```
With options:
```bash
exportcomments export https://www.youtube.com/watch?v=dQw4w9WgXcQ --replies --limit 500
```
Wait for completion and download:
```bash
exportcomments export https://www.amazon.com/dp/B08N5WRWNW --wait --download
```
Download raw JSON data:
```bash
exportcomments export https://www.trustpilot.com/review/example.com --wait --download-json
```
Filter by date range:
```bash
exportcomments export https://www.reddit.com/r/sub/comments/abc123/ \
--min-date 2024-01-01 --max-date 2024-06-30
```
Twitter/X specific options:
```bash
exportcomments export https://x.com/username --tweets --followers --limit 1000
```
Real-time monitoring:
```bash
exportcomments export https://www.amazon.com/dp/B08N5WRWNW --wait --realtime
```
**Response (HTTP 200):**
```json
{
"ok": true,
"data": {
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "queueing",
"url": "https://www.instagram.com/p/ABC123/"
}
}
```
> **Waiting for completion:**
When using `--wait`, the CLI polls the API every 5 seconds (configurable with `--wait-interval`) until the job reaches a `done` or `error` status, or the timeout is reached.
---
# status
Check the current status of an export job by its GUID. Alias: `check`.
```bash
exportcomments status [options]
```
## Options
- **--wait** (flag, required=false): Wait for the job to complete
- **--realtime** (flag, required=false): Use WebSocket for real-time status updates
- **--wait-interval ** (number, required=false): Custom polling interval in milliseconds
- **--wait-timeout ** (number, required=false): Custom timeout in milliseconds (default: 600000)
## Examples
Check status once:
```bash
exportcomments status b4219d47-3138-5efd-9762-2ef9f9495084
```
Wait for completion:
```bash
exportcomments status b4219d47-3138-5efd-9762-2ef9f9495084 --wait
```
**Response (HTTP 200):**
```json
{
"ok": true,
"data": {
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.instagram.com/p/ABC123/",
"totalComments": 342,
"exportedComments": 342,
"downloadUrl": "https://exportcomments.com/api/v3/job/b4219d47-.../download"
}
}
```
---
# list
List all export jobs for your account with pagination. Alias: `ls`.
```bash
exportcomments list [options]
```
## Options
- **--page ** (number, required=false): Page number (default: 1)
- **--limit ** (number, required=false): Items per page (default: 20)
## Examples
List recent jobs:
```bash
exportcomments list
```
With pagination:
```bash
exportcomments list --page 2 --limit 10
```
**Response (HTTP 200):**
```json
{
"ok": true,
"data": [
{
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.instagram.com/p/ABC123/"
},
{
"id": 12344,
"guid": "a3118c36-2027-4dfc-8651-1de8e8384073",
"status": "progress",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
]
}
```
---
# download
Download the results of a completed export job. Alias: `dl`.
```bash
exportcomments download [options]
```
## Options
- **--json** (flag, required=false): Download raw JSON data instead of Excel/CSV
- **-o, --output ** (string, required=false): Output file path
## Examples
Download formatted file (Excel/CSV):
```bash
exportcomments download b4219d47-3138-5efd-9762-2ef9f9495084
```
Download raw JSON data:
```bash
exportcomments download b4219d47-3138-5efd-9762-2ef9f9495084 --json
```
Save to a specific path:
```bash
exportcomments download b4219d47-3138-5efd-9762-2ef9f9495084 -o ./exports/data.xlsx
```
**Response (--json) (HTTP 200):**
```json
{
"ok": true,
"data": [
{
"text": "Great video!",
"author": "user123",
"date": "2024-03-15T10:30:00Z",
"likes": 42
}
]
}
```
> **Job must be completed:**
The download command only works for jobs with a `done` status. If the job is still in progress, use the [status](/cli/status) command with `--wait` first.
---
# platforms
List all supported platforms, detect a platform from a URL, or get detailed info about a specific platform.
```bash
exportcomments platforms [options]
```
## Options
- **--detect ** (string, required=false): Detect the platform for a given URL
- **--id ** (string, required=false): Get detailed info for a specific platform
## Examples
List all supported platforms:
```bash
exportcomments platforms
```
Detect platform from a URL:
```bash
exportcomments platforms --detect "https://www.youtube.com/watch?v=abc"
```
Get info about a specific platform:
```bash
exportcomments platforms --id instagram
```
**Response (--detect) (HTTP 200):**
```json
{
"ok": true,
"data": {
"id": "youtube",
"name": "YouTube",
"description": "Export comments from YouTube videos",
"options": ["replies", "limit"],
"examples": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
]
}
}
```
> **Use before exporting:**
The `--detect` flag is useful for checking which options are available for a URL before creating an export job.
---
# MCP Server
The CLI includes an [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that gives AI assistants native access to ExportComments tools. Works with Claude Desktop, Claude Code, Cursor, Windsurf, and any MCP-compatible client.
## Installation
```bash
npm install -g exportcomments-cli
```
Or use `npx` for zero-install setup (recommended for MCP clients):
```bash
npx -y exportcomments-cli
```
> **npm package:**
The MCP server is part of the [`exportcomments-cli`](https://www.npmjs.com/package/exportcomments-cli) npm package. Node.js 18+ required.
## Setup
### Claude Desktop
Add to your `claude_desktop_config.json`:
**json:**
```json
{
"mcpServers": {
"exportcomments": {
"command": "npx",
"args": ["-y", "exportcomments-cli"],
"env": {
"EXPORTCOMMENTS_API_TOKEN": "your-token-here"
}
}
}
}
```
### Claude Code
```bash
claude mcp add exportcomments -- npx -y exportcomments-cli
```
Then set the environment variable:
```bash
export EXPORTCOMMENTS_API_TOKEN="your-token-here"
```
### Cursor
Add to your `.cursor/mcp.json` in your project root or global settings:
**json:**
```json
{
"mcpServers": {
"exportcomments": {
"command": "npx",
"args": ["-y", "exportcomments-cli"],
"env": {
"EXPORTCOMMENTS_API_TOKEN": "your-token-here"
}
}
}
}
```
### Windsurf
Add to your Windsurf MCP configuration:
**json:**
```json
{
"mcpServers": {
"exportcomments": {
"command": "npx",
"args": ["-y", "exportcomments-cli"],
"env": {
"EXPORTCOMMENTS_API_TOKEN": "your-token-here"
}
}
}
}
```
### Run Directly
```bash
EXPORTCOMMENTS_API_TOKEN="your-token" exportcomments-mcp
```
## Authentication
Get your API token from the [API dashboard](https://app.exportcomments.com/user/api). The MCP server reads it from the `EXPORTCOMMENTS_API_TOKEN` environment variable.
## Available Tools
The MCP server exposes 6 tools to AI assistants:
| Tool | Description |
|------|-------------|
| `export_comments` | Create an export job for a URL with full option support |
| `check_export` | Check job status by GUID, optionally wait for completion |
| `list_exports` | List all export jobs with pagination |
| `download_export` | Download raw JSON data for a completed job |
| `detect_platform` | Identify platform from a URL and show available options |
| `list_platforms` | List all 33+ supported platforms |
## Tool Parameters
### export_comments
Create a new export job to extract comments or reviews from a URL. Supports 33+ platforms including Instagram, YouTube, TikTok, Facebook, Twitter/X, Reddit, Amazon, Trustpilot, and more. Returns a job GUID for tracking.
- **url** (string, required=true): The URL to export comments/reviews from
- **replies** (boolean, required=false): Include replies to comments
- **limit** (number, required=false): Maximum number of items to export
- **min_date** (string, required=false): Minimum date filter (ISO 8601, e.g. 2024-01-15)
- **max_date** (string, required=false): Maximum date filter (ISO 8601, e.g. 2024-06-30)
- **vpn** (string, required=false): Use VPN with specified country (e.g. "Norway")
- **cookies** (object, required=false): Cookies for authenticated access (e.g. {"sessionid": "abc"})
- **tweets** (boolean, required=false): Include tweets (Twitter/X only)
- **followers** (boolean, required=false): Export followers list (Twitter/X only)
- **following** (boolean, required=false): Export following list (Twitter/X only)
- **likes** (boolean, required=false): Export likes data
- **shares** (boolean, required=false): Include shares data
- **advanced** (boolean, required=false): Enable advanced export features
- **facebook_ads** (boolean, required=false): Include Facebook ads data
- **wait** (boolean, required=false): Wait for export to complete before returning (polls every 5s, timeout 10min)
- **realtime** (boolean, required=false): Use WebSocket for real-time updates instead of polling (implies wait=true)
### check_export
Check the status of an export job by its GUID. Returns full job details including status, progress, download URLs, and error info. Job statuses: `queueing` → `progress` → `done` | `error`.
- **guid** (string, required=true): The job GUID returned by export_comments
- **wait** (boolean, required=false): Wait for export to complete before returning
- **realtime** (boolean, required=false): Use WebSocket for real-time updates instead of polling (implies wait=true)
### list_exports
List all export jobs for the authenticated account with pagination.
- **page** (number, required=false): Page number (default: 1)
- **limit** (number, required=false): Items per page (default: 20)
### download_export
Download the raw JSON data for a completed export job. Returns the actual exported comments/reviews as structured JSON data. The job must have status `done`.
- **guid** (string, required=true): The job GUID to download data for
### detect_platform
Detect which platform a URL belongs to and return supported options. Use this before `export_comments` to understand what options are available for a URL.
- **url** (string, required=true): The URL to detect the platform for
### list_platforms
List all 33+ supported platforms with their URL patterns, export options, and example URLs. No parameters required.
## Example Workflows
### Export and download comments
An AI agent can chain tools together for a complete workflow:
1. **detect_platform** — check what options the URL supports
2. **export_comments** with `wait=true` — create the export and wait for completion
3. **download_export** — retrieve the raw JSON data
### Monitor an existing job
1. **check_export** with `wait=true` — wait until the job finishes
2. **download_export** — get the results
### Discover platforms
1. **list_platforms** — see all supported platforms and their URL patterns
2. **detect_platform** — verify a specific URL is supported before exporting
## Response Format
All MCP tool responses return structured JSON:
```json
{
"ok": true,
"data": {
"id": 12345,
"guid": "b4219d47-3138-5efd-9762-2ef9f9495084",
"status": "done",
"url": "https://www.instagram.com/p/ABC123/",
"totalComments": 342,
"exportedComments": 342,
"downloadUrl": "https://exportcomments.com/api/v3/job/b4219d47-.../download"
}
}
```
On error:
```json
{
"ok": false,
"error": "Human-readable error message",
"error_code": "MACHINE_READABLE_CODE",
"detail": "Additional context"
}
```
> **AI-optimized output:**
All MCP tool responses return structured JSON, making it easy for AI assistants to parse results and chain multiple operations together. Use `wait=true` on export_comments and check_export to get final results in a single call.
---
# VPN Locations
GET /api/public/vpn/locations
Retrieve all available VPN locations that can be used for export routing. Use VPN locations to access geo-restricted content on platforms that limit visibility based on the requester's country.
> **Public endpoint:**
This endpoint is public and does not require authentication.
## Query Parameters
- **page** (integer, required=false): Page number (default: 1)
- **limit** (integer, required=false): Items per page (default: 10, max: 30)
**curl:**
```
curl https://exportcomments.com/api/public/vpn/locations
```
**python:**
```
import requests
response = requests.get("https://exportcomments.com/api/public/vpn/locations")
locations = response.json()
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/public/vpn/locations');
const locations = await response.json();
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/public/vpn/locations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
]);
$locations = json_decode(curl_exec($ch), true);
```
**Response (HTTP 200):**
```json
[
{
"name": "United States",
"code": "US"
},
{
"name": "United Kingdom",
"code": "GB"
},
{
"name": "Germany",
"code": "DE"
},
{
"name": "Norway",
"code": "NO"
}
]
```
## Usage
Pass the VPN location `name` in the `options.vpn` field when creating an export job:
```json
{
"url": "https://www.youtube.com/watch?v=example",
"options": {
"vpn": "Norway"
}
}
```
> **When to use VPN locations:**
VPN locations are useful when exporting content that is region-locked or when the platform serves different results based on the requester's geographic location. Fetch the list of available locations first and use the `name` value exactly as returned.
---
# API Usage
GET /api/v1/api-usage
Retrieve your current API usage statistics including tier information, limit configuration, daily request consumption, active exports, and platform access. Use this endpoint to monitor consumption and stay within your tier's limits.
**curl:**
```
curl https://exportcomments.com/api/v1/api-usage \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
response = requests.get(
"https://exportcomments.com/api/v1/api-usage",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
usage = response.json()
print(usage["tier"]) # "business"
print(usage["usage"]["daily_requests_used"]) # 42
print(usage["limits"]["daily_requests"]) # "Unlimited"
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/api-usage', {
headers: { 'X-AUTH-TOKEN': 'your-api-key' },
});
const usage = await response.json();
console.log(usage.tier); // "business"
console.log(usage.usage.daily_requests_used); // 42
console.log(usage.limits.daily_requests); // "Unlimited"
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/api-usage');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$usage = json_decode(curl_exec($ch), true);
echo $usage['tier']; // "business"
echo $usage['usage']['daily_requests_used']; // 42
echo $usage['limits']['daily_requests']; // "Unlimited"
```
## Response Examples
### Business tier (full access)
**200 OK — Business tier (HTTP 200):**
```json
{
"has_access": true,
"has_token": true,
"tier": "business",
"api_version": "v3",
"limits": {
"daily_requests": "Unlimited",
"concurrent_requests": 5,
"rate_per_minute": 200,
"max_comments_per_export": 250000,
"webhooks": 10
},
"usage": {
"daily_requests_used": 42,
"active_exports": 1
},
"platforms": {
"allowed": ["YouTube", "Instagram", "Facebook", "TikTok", "Twitter", "Reddit", "LinkedIn"],
"restricted": [],
"all_platforms": true
},
"upgrade_benefits": null
}
```
### Premium tier (upgrade benefits included)
**200 OK — Premium tier (HTTP 200):**
```json
{
"has_access": true,
"has_token": true,
"tier": "premium",
"api_version": "v3",
"limits": {
"daily_requests": 100,
"concurrent_requests": 1,
"rate_per_minute": 60,
"max_comments_per_export": 5000,
"webhooks": 1
},
"usage": {
"daily_requests_used": 18,
"active_exports": 0
},
"platforms": {
"allowed": ["YouTube", "Instagram", "Facebook", "TikTok", "Twitter", "Reddit", "LinkedIn"],
"restricted": [],
"all_platforms": false
},
"upgrade_benefits": {
"unlimited_daily_requests": true,
"more_concurrent_requests": "10 vs 1",
"higher_rate_limit": "200/min vs 60/min",
"more_comments": "250,000 vs 5,000",
"all_platforms": true,
"more_webhooks": "10 vs 1"
}
}
```
### No API access (subscription required)
**200 OK — No access (HTTP 200):**
```json
{
"has_access": false,
"required_plan": "premium",
"message": "Upgrade to Premium or Business plan to access the API."
}
```
### API token not yet generated
**200 OK — Token missing (HTTP 200):**
```json
{
"has_access": true,
"has_token": false,
"tier": "premium",
"message": "Generate an API token to start using the API."
}
```
## Response Fields
### Top-level fields
| Field | Type | Description |
|-------|------|-------------|
| `has_access` | boolean | Whether the account has API access via an active subscription |
| `has_token` | boolean | Whether an API token has been generated. Omitted when `has_access` is `false` |
| `tier` | string | Active subscription tier: `premium` or `business`. Omitted when `has_access` is `false` |
| `api_version` | string | API version the token is provisioned for (e.g. `v3`) |
| `limits` | object | Configured limits for the token. See [Limits object](#limits-object) |
| `usage` | object | Current consumption counters. See [Usage object](#usage-object) |
| `platforms` | object | Platform access configuration. See [Platforms object](#platforms-object) |
| `upgrade_benefits` | object\|null | Populated for `premium` tier to describe Business upgrade gains. `null` for Business tier |
### Limits object
| Field | Type | Description |
|-------|------|-------------|
| `daily_requests` | integer\|string | Maximum API requests per day. `"Unlimited"` for Business tier |
| `concurrent_requests` | integer | Maximum simultaneous export jobs |
| `rate_per_minute` | integer | Maximum requests allowed per minute |
| `max_comments_per_export` | integer | Maximum comments retrievable in a single export |
| `webhooks` | integer | Maximum registered webhooks |
### Usage object
| Field | Type | Description |
|-------|------|-------------|
| `daily_requests_used` | integer | Number of API requests made today |
| `active_exports` | integer | Number of export jobs currently running |
### Platforms object
| Field | Type | Description |
|-------|------|-------------|
| `allowed` | array | Display names of platforms the token can export from |
| `restricted` | array | Display names of platforms blocked for this token. Empty array when unrestricted |
| `all_platforms` | boolean | `true` if the token has access to all available platforms (Business tier) |
### Upgrade benefits object (Premium tier only)
| Field | Type | Description |
|-------|------|-------------|
| `unlimited_daily_requests` | boolean | Business tier removes the daily request cap |
| `more_concurrent_requests` | string | Concurrent export comparison (e.g. `"10 vs 1"`) |
| `higher_rate_limit` | string | Rate limit comparison (e.g. `"200/min vs 60/min"`) |
| `more_comments` | string | Max comments comparison (e.g. `"250,000 vs 5,000"`) |
| `all_platforms` | boolean | Business tier grants access to all 30+ platforms |
| `more_webhooks` | string | Webhook limit comparison (e.g. `"10 vs 1"`) |
> **Unlimited daily requests:**
On Business tier, `limits.daily_requests` returns the string `"Unlimited"` rather than an integer. Check for this string before performing numeric comparisons in your code.
## Tier Comparison
| Metric | Premium | Business |
|--------|---------|----------|
| Daily requests | 100 | Unlimited |
| Rate limit | 60/min | 200/min |
| Concurrent exports | 1 | 5 |
| Max comments | 5,000 | 250,000 |
| Webhooks | 1 | 10 |
| All platforms | No | Yes |
See [Tiers](/tiers) for the full feature comparison across plans.
---
# Bulk Download
Download multiple completed exports as a single ZIP file. This is useful when you need to collect results from several export jobs without making individual download requests for each one.
## Workflow
1. **Create** a bulk download request with export GUIDs
2. **Poll status** until the ZIP is ready
3. **Download** the ZIP file
```
POST /api/v1/bulk-download → { uuid, status: "processing" }
GET /api/v1/bulk-download/{uuid} → { status: "ready", downloadUrl }
GET /api/v1/bulk-download/{uuid}/download → ZIP binary
```
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v1/bulk-download` | Create a bulk download request |
| `GET` | `/api/v1/bulk-download/{uuid}` | Check bulk download status |
| `GET` | `/api/v1/bulk-download/{uuid}/download` | Download the ZIP file |
## Status Values
| Status | Description |
|--------|-------------|
| `processing` | ZIP is being assembled |
| `ready` | ZIP is ready to download |
| `error` | ZIP assembly failed |
> **Export jobs must be completed:**
Only jobs with a `done` status can be included in a bulk download. Passing the GUID of a job that is still processing or has errored will cause that file to be skipped or the request to fail.
---
# Create a Bulk Download
POST /api/v1/bulk-download
Create a bulk download request for multiple completed exports. The server assembles all matching export files into a single ZIP archive asynchronously.
## Request Body
- **guids** (array, required=true): Array of export job GUIDs to include in the ZIP
**curl:**
```
curl -X POST https://exportcomments.com/api/v1/bulk-download \
-H "X-AUTH-TOKEN: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"guids": [
"b4219d47-3138-5efd-9762-2ef9f9495084",
"a3118c36-2027-4dfc-8651-1de8e8384973"
]
}'
```
**python:**
```
import requests
response = requests.post(
"https://exportcomments.com/api/v1/bulk-download",
headers={"X-AUTH-TOKEN": "your-api-key"},
json={
"guids": [
"b4219d47-3138-5efd-9762-2ef9f9495084",
"a3118c36-2027-4dfc-8651-1de8e8384973"
]
}
)
download = response.json()
print(f"Bulk download created: {download['uuid']}")
```
**node:**
```
const response = await fetch('https://exportcomments.com/api/v1/bulk-download', {
method: 'POST',
headers: {
'X-AUTH-TOKEN': 'your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
guids: [
'b4219d47-3138-5efd-9762-2ef9f9495084',
'a3118c36-2027-4dfc-8651-1de8e8384973',
],
}),
});
const download = await response.json();
console.log(`Bulk download created: ${download.uuid}`);
```
**php:**
```
$ch = curl_init('https://exportcomments.com/api/v1/bulk-download');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-AUTH-TOKEN: your-api-key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'guids' => [
'b4219d47-3138-5efd-9762-2ef9f9495084',
'a3118c36-2027-4dfc-8651-1de8e8384973',
],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$download = json_decode(curl_exec($ch), true);
echo "Bulk download created: " . $download['uuid'];
```
**Response (HTTP 201):**
```json
{
"uuid": "d1e2f3a4-5678-90ab-cdef-1234567890ab",
"status": "processing",
"fileCount": 2
}
```
> **ZIP assembly is asynchronous:**
The bulk download starts in `processing` status. Use the [Check Status](/bulk-download/status) endpoint to poll until the status changes to `ready` before attempting to download.
---
# Check Bulk Download Status
GET /api/v1/bulk-download/{uuid}
Check the status of a bulk download request. Poll this endpoint until the `status` field is `ready`, then proceed to the download endpoint.
## Path Parameters
- **uuid** (string, required=true): Bulk download identifier
**curl:**
```
curl https://exportcomments.com/api/v1/bulk-download/d1e2f3a4-5678-90ab-cdef-1234567890ab \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
import time
uuid = "d1e2f3a4-5678-90ab-cdef-1234567890ab"
while True:
response = requests.get(
f"https://exportcomments.com/api/v1/bulk-download/{uuid}",
headers={"X-AUTH-TOKEN": "your-api-key"}
)
status = response.json()
if status["status"] == "ready":
print(f"Ready to download: {status['downloadUrl']}")
break
time.sleep(5)
```
**node:**
```
const uuid = 'd1e2f3a4-5678-90ab-cdef-1234567890ab';
async function waitForReady() {
while (true) {
const response = await fetch(
`https://exportcomments.com/api/v1/bulk-download/${uuid}`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const status = await response.json();
if (status.status === 'ready') {
console.log(`Ready to download: ${status.downloadUrl}`);
return status;
}
await new Promise((r) => setTimeout(r, 5000));
}
}
```
**php:**
```
$uuid = 'd1e2f3a4-5678-90ab-cdef-1234567890ab';
do {
$ch = curl_init("https://exportcomments.com/api/v1/bulk-download/{$uuid}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$status = json_decode(curl_exec($ch), true);
if ($status['status'] !== 'ready') {
sleep(5);
}
} while ($status['status'] !== 'ready');
echo "Ready to download: " . $status['downloadUrl'];
```
**Response (HTTP 200):**
```json
{
"uuid": "d1e2f3a4-5678-90ab-cdef-1234567890ab",
"status": "ready",
"fileCount": 2,
"downloadUrl": "/api/v1/bulk-download/d1e2f3a4-5678-90ab-cdef-1234567890ab/download"
}
```
---
# Download Bulk File
GET /api/v1/bulk-download/{uuid}/download
Download the completed bulk export ZIP file. Only call this endpoint once the [status check](/bulk-download/status) returns `"status": "ready"`.
## Path Parameters
- **uuid** (string, required=true): Bulk download identifier
**curl:**
```
curl -O https://exportcomments.com/api/v1/bulk-download/d1e2f3a4-5678-90ab-cdef-1234567890ab/download \
-H "X-AUTH-TOKEN: your-api-key"
```
**python:**
```
import requests
uuid = "d1e2f3a4-5678-90ab-cdef-1234567890ab"
response = requests.get(
f"https://exportcomments.com/api/v1/bulk-download/{uuid}/download",
headers={"X-AUTH-TOKEN": "your-api-key"},
stream=True
)
with open("exports.zip", "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print("Downloaded exports.zip")
```
**node:**
```
const fs = require('fs');
const uuid = 'd1e2f3a4-5678-90ab-cdef-1234567890ab';
const response = await fetch(
`https://exportcomments.com/api/v1/bulk-download/${uuid}/download`,
{ headers: { 'X-AUTH-TOKEN': 'your-api-key' } }
);
const buffer = await response.arrayBuffer();
fs.writeFileSync('exports.zip', Buffer.from(buffer));
console.log('Downloaded exports.zip');
```
**php:**
```
$uuid = 'd1e2f3a4-5678-90ab-cdef-1234567890ab';
$ch = curl_init("https://exportcomments.com/api/v1/bulk-download/{$uuid}/download");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-AUTH-TOKEN: your-api-key'],
CURLOPT_RETURNTRANSFER => true,
]);
$zipContent = curl_exec($ch);
file_put_contents('exports.zip', $zipContent);
echo "Downloaded exports.zip";
```
Returns the ZIP file as binary content with `Content-Type: application/zip`.
> **File availability:**
Bulk download files are available for a limited time after the status shows `ready`. Download promptly after the ZIP is ready to avoid the file expiring.
---