Create, list, update, delete, and report on Browser Simulation and Google Analytics campaigns from any HTTP client.
The TrafficBot REST API mirrors what the dashboard does for campaigns. Anything you can do in the UI — list, create, update, toggle, delete, and pull hourly reports — you can do over HTTP with a bearer token.
Two services are exposed: Browser Simulation (bsim-campaigns) and Google Analytics (ga-campaigns). Social Media orders are not part of the API. Subscriptions and credit purchases stay in the browser checkout flow.
Reads and writes go through the same service layer the dashboard uses. No drift.
Six abilities (read, write, delete × two services). Mint read-only tokens for analytics scripts.
All endpoints under /api/v1/. Versioned. Breaking changes ship under /api/v2/.
Tokens are minted from your dashboard. Pick the abilities you want, copy the plaintext token once, and pass it as a bearer header on every request.
Visit /dashboard/api-tokens/create. Give the token a recognizable name (e.g., "MCP integration"), tick the abilities you need, and (optionally) set an expiry date.
After clicking Create token the plaintext is shown once in a banner. Copy it then. We only store the hash, so you can't retrieve it later — only revoke and recreate.
| Ability | Grants |
|---|---|
| ga-campaigns:read | List, get, and report on GA campaigns |
| ga-campaigns:write | Create and update GA campaigns |
| ga-campaigns:delete | Delete GA campaigns |
| bsim-campaigns:read | List, get, and report on Bsim campaigns |
| bsim-campaigns:write | Create and update Bsim campaigns |
| bsim-campaigns:delete | Delete Bsim campaigns |
Abilities are independent. write does not imply read. /api/v1/me and /api/v1/bsim/proxy-countries work with any valid token.
Pass the token in the Authorization header. Always set Accept: application/json so error responses come back as JSON instead of HTML.
curl https://trafficbot.co/api/v1/me \
-H "Authorization: Bearer 1|abc123def456..." \
-H "Accept: application/json"
Treat tokens like passwords. Anyone with the plaintext can act as you within the granted abilities. Revoke immediately at /dashboard/api-tokens if a token leaks.
Want to try requests in your browser? The interactive Swagger UI lives at /api/documentation. Click Authorize, paste your token (without the Bearer prefix — the UI adds it), and use Try it out on any endpoint.
Prefer the spec? Import /api/v1/openapi.json into Postman, Insomnia, or any OpenAPI-aware tool.
Same shape across every endpoint. Knowing these four rules covers most of the API.
All endpoints sit under https://trafficbot.co/api/v1/. Breaking changes ship under /api/v2/; we won't change v1 shapes without a new major.
A single resource is wrapped in { "data": {...} }:
{
"data": {
"id": 42,
"name": "Homepage promo",
"is_active": true,
"...": "..."
}
}
List endpoints return Laravel's standard paginated envelope. data is the page; links has prev/next page URLs; meta has pagination metadata.
{
"data": [ { "id": 1, "...": "..." }, { "id": 2, "...": "..." } ],
"links": {
"first": "https://trafficbot.co/api/v1/ga-campaigns?page=1",
"last": "https://trafficbot.co/api/v1/ga-campaigns?page=4",
"prev": null,
"next": "https://trafficbot.co/api/v1/ga-campaigns?page=2"
},
"meta": {
"current_page": 1, "from": 1, "to": 5,
"per_page": 5, "last_page": 4, "total": 18,
"path": "https://trafficbot.co/api/v1/ga-campaigns",
"links": [ /* paginator nav entries */ ]
}
}
Common query params: page, search, status=active|inactive, sort, direction=asc|desc.
Every non-2xx response uses the same shape:
{
"message": "The given data was invalid.",
"errors": {
"name": ["The name field is required."]
}
}
See Error responses for the full list of status codes and field keys.
Cheat sheet of every endpoint. Both services have the same six routes; account and reference endpoints sit alongside.
| Method | Path | Ability | Description |
|---|---|---|---|
| GET | /ga-campaigns | ga-campaigns:read | Paginated list |
| POST | /ga-campaigns | ga-campaigns:write | Create campaign |
| GET | /ga-campaigns/{id} | ga-campaigns:read | Get one campaign |
| PATCH | /ga-campaigns/{id} | ga-campaigns:write | Partial update |
| DELETE | /ga-campaigns/{id} | ga-campaigns:delete | Delete campaign |
| GET | /ga-campaigns/{id}/report | ga-campaigns:read | Hourly report (≤ 90 days) |
| Method | Path | Ability | Description |
|---|---|---|---|
| GET | /bsim-campaigns | bsim-campaigns:read | Paginated list |
| POST | /bsim-campaigns | bsim-campaigns:write | Create campaign |
| GET | /bsim-campaigns/{id} | bsim-campaigns:read | Get one campaign |
| PATCH | /bsim-campaigns/{id} | bsim-campaigns:write | Partial update |
| DELETE | /bsim-campaigns/{id} | bsim-campaigns:delete | Deactivate first; deleting an active campaign returns 422 |
| GET | /bsim-campaigns/{id}/report | bsim-campaigns:read | Hourly report (≤ 90 days) |
| Method | Path | Ability | Description |
|---|---|---|---|
| GET | /me | none | Profile + credit balances |
| GET | /bsim/proxy-countries | none | Available proxy country codes |
curl "https://trafficbot.co/api/v1/ga-campaigns?status=active&page=1" \
-H "Authorization: Bearer 1|abc..." \
-H "Accept: application/json"
curl -X POST https://trafficbot.co/api/v1/ga-campaigns \
-H "Authorization: Bearer 1|abc..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Homepage promo",
"tracker_id": "G-XXXXXXX",
"events_per_day": 100,
"bounce_rate": 0.5,
"max_events_per_visitor": 5,
"fluctuate_event_rate": 0.1,
"event_duration": 60,
"fluctuate_duration_rate": 0.1,
"device_rate": 0.5,
"timezone": "America/New_York",
"locations": ["US"],
"urls": [{"url": "https://example.com", "title": "Home"}],
"daily_rates": {"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1},
"is_active": true
}'
Returns 201 Created with the new campaign in data.
curl -X PATCH https://trafficbot.co/api/v1/ga-campaigns/42 \
-H "Authorization: Bearer 1|abc..." \
-H "Content-Type: application/json" \
-d '{"is_active": false}'
PATCH supports partial payloads — send only the fields you want to change.
curl "https://trafficbot.co/api/v1/ga-campaigns/42/report?start_date=2026-04-01&end_date=2026-04-30" \
-H "Authorization: Bearer 1|abc..." \
-H "Accept: application/json"
Default window: last 7 days in the campaign's timezone. Maximum range: 90 days.
curl -X POST https://trafficbot.co/api/v1/bsim-campaigns \
-H "Authorization: Bearer 1|abc..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "US homepage",
"url": "https://example.com",
"visits_per_day": 100,
"bounce_rate": 0.5,
"max_pages_per_visit": 3,
"dwell_time_min": 5,
"dwell_time_max": 30,
"device_rate": 0.5,
"countries": ["US"],
"fluctuate_visit_rate": 0.1,
"daily_rates": {"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1},
"is_active": true
}'
Validate countries first. Bsim campaigns only run in countries we have residential proxies for. Pull the live list from /api/v1/bsim/proxy-countries before submitting, and pick from that array.
Bsim campaigns must be inactive before they can be deleted. PATCH first, then DELETE.
curl -X PATCH https://trafficbot.co/api/v1/bsim-campaigns/42 \
-H "Authorization: Bearer 1|abc..." \
-H "Content-Type: application/json" \
-d '{"is_active": false}'
curl -X DELETE https://trafficbot.co/api/v1/bsim-campaigns/42 \
-H "Authorization: Bearer 1|abc..."
Deleting an active Bsim campaign returns 422 with errors.is_active.
Three tiered limits apply per request. Hitting any one returns 429 Too Many Attempts. with a Retry-After header (seconds until you can retry).
| Bucket | Limit | Applies to |
|---|---|---|
| Read (per token) | 120 / minute | All GET endpoints |
| Write (per token) | 30 / minute | POST, PATCH, DELETE |
| Per IP | 300 / minute | Defense-in-depth across all endpoints |
All errors share the { message, errors } envelope. The status code tells you the category; errors keys tell you which field needs attention.
| Status | When | Common errors keys |
|---|---|---|
| 401 | Missing or invalid token | — |
| 403 | Token lacks the required ability | — |
| 404 | Resource doesn't exist or isn't yours | — |
| 422 | Validation or domain rule failed | name, url, is_active, limit, end_date |
| 429 | Rate limit exceeded | — (see Retry-After) |
HTTP 422
{ "message": "...", "errors": { "is_active": ["..."] } }
HTTP 422
{ "message": "Campaign limit reached...", "errors": { "limit": ["..."] } }
HTTP 422
{ "message": "Date range cannot exceed 90 days.", "errors": { "end_date": ["..."] } }
No separate sandbox. Production is the only environment — but campaigns can be created with is_active: false for safe round-trip testing without spending credits.
Not officially. The OpenAPI 3.0 spec at /api/v1/openapi.json works with code generators (openapi-generator, openapi-typescript, etc.) if you want a typed client.
No. Billing stays in the browser via PayPal. The API is scoped to campaign management. Use /me to read current credit balances.
100 per service per user (so 100 GA + 100 Bsim). Hitting the cap returns 422 with errors.limit. Need more? Talk to us.
Go to /dashboard/api-tokens, click Revoke on the token in question. Revocation is immediate. The "Last used" column helps you spot tokens that look unfamiliar.
Mint a token, copy the bearer string, start hitting /api/v1/.