A simple read-only API.
The WeForAds Publisher Revenue API v1 gives you programmatic access to the same revenue, eCPM, impression, and per-site numbers you already see in the publisher dashboard. It is designed for one job: getting your data out of WeForAds and into wherever you keep the rest of your business numbers — a warehouse, a BI tool, a finance spreadsheet, a Slack bot.
Every endpoint is HTTP GET, returns JSON, and is authenticated with a single bearer token. There is no SDK to install, no OAuth dance, no webhooks to configure. If you can curl, you can use it.
Base URL
https://api.weforads.com/api/v1
Conventions
- All responses are
application/json; charset=utf-8. - All monetary amounts are returned as decimal strings in USD (e.g.
"12.4738") to avoid floating-point loss. Always parse as decimal, never asfloat. - All timestamps and dates are UTC. Date-only fields use
YYYY-MM-DD. Timestamps use ISO 8601 (2026-06-02T14:23:11Z). - "Today" means the current UTC calendar day. If your finance day is in a different timezone, slice your own ranges with the date-range endpoint.
- Revenue numbers refresh approximately every 60 minutes (programmatic) and every 4 hours (Direct Deals). Same cadence as the dashboard.
One header. Bearer token.
Every request must include an Authorization header with a bearer token. Generate your key from Publisher Dashboard → API Access — keys begin with the prefix weforads_pk_.
Authorization: Bearer weforads_pk_a8f4c6b2e7d1...
Key scope
- One key authorizes one publisher account. All sites on that account are visible to the key.
- Keys are read-only on the revenue endpoints — they cannot change settings or trigger payouts.
- You can issue up to 5 active keys per account. Revoke unused keys; they cannot be recovered after revocation.
- Team members (per the publisher_members table) do not automatically inherit the owner's keys. Each team member generates their own.
Security guidance
- Never embed an API key in client-side JavaScript, mobile apps, or anything a browser can read. Treat it like a database password.
- Prefer environment variables or a secrets manager. Rotate quarterly.
- If a key leaks, revoke it immediately from the API Access page. We do not bill, debit, or settle against API activity, so a leaked key cannot move money — but it can expose your revenue numbers.
60 per minute. 1,000 per day.
Limits are applied per API key, not per IP. The two windows are independent — you can exhaust the per-minute window without touching the daily window, and vice versa.
| Window | Limit | Reset |
|---|---|---|
| Per minute | 60 requests | Sliding 60s |
| Per day | 1,000 requests | 00:00 UTC |
Response headers
Every successful response includes three rate-limit headers so you can self-throttle without polling:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1717340580 # unix seconds
If you exceed a limit you'll receive an HTTP 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait. The body will be {"error":"rate_limited",...} — see the error codes section.
Need more? The revenue numbers themselves only refresh ~hourly, so polling more than once per minute is rarely useful. If you have a genuine batch use case, email [email protected].
Two endpoints. That's all.
Today and range. Everything else you might want — yesterday, MTD, last 7 days, a custom finance month — is just a date-range slice.
Returns today's revenue, eCPM, and impressions, summed across all of your sites. Convenience wrapper around the range endpoint with start = end = today (UTC).
Parameters
None.
Example response
{
"date": "2026-06-02",
"timezone": "UTC",
"revenue_usd": "412.8341",
"impressions": 1284032,
"ecpm_usd": "0.3215",
"sources": {
"programmatic_usd": "398.2104",
"direct_deals_usd": "14.6237"
},
"sites": [
{ "site_id": 244, "domain": "example.com", "revenue_usd": "301.4422", "impressions": 951204 },
{ "site_id": 311, "domain": "example.net", "revenue_usd": "111.3919", "impressions": 332828 }
],
"as_of": "2026-06-02T14:23:11Z"
}
Returns daily revenue rows for an inclusive UTC date range. Use this for yesterday, MTD, last 30 days, or your own custom finance window. Maximum range: 92 days per call.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| start | date | required | YYYY-MM-DD. Inclusive. Earliest supported: 2024-01-01. |
| end | date | required | YYYY-MM-DD. Inclusive. Must be ≥ start and within 92 days of it. |
| site_id | integer | optional | Filter to one site. Omit for account-wide totals. |
| granularity | enum | optional | day (default) or total (one summed row). |
Example request
GET /api/v1/publisher/revenue?start=2026-05-01&end=2026-05-31 HTTP/1.1
Host: api.weforads.com
Authorization: Bearer weforads_pk_xxx
Example response
{
"range": { "start": "2026-05-01", "end": "2026-05-31" },
"granularity": "day",
"timezone": "UTC",
"totals": {
"revenue_usd": "11842.3104",
"impressions": 38214771,
"ecpm_usd": "0.3099"
},
"days": [
{ "date": "2026-05-01", "revenue_usd": "382.4109", "impressions": 1248122, "ecpm_usd": "0.3064" },
{ "date": "2026-05-02", "revenue_usd": "401.1283", "impressions": 1291844, "ecpm_usd": "0.3105" }
// ... 29 more rows
]
}
Three lines. Three languages.
Pulling today's revenue from a shell script, a Python data pipeline, or a Node service. The shapes are identical because the API does not care.
# Today's revenue, account-wide
curl -H "Authorization: Bearer weforads_pk_xxx" \
https://api.weforads.com/api/v1/publisher/revenue/today
# May 2026, day-by-day, JSON pretty-printed
curl -sH "Authorization: Bearer weforads_pk_xxx" \
"https://api.weforads.com/api/v1/publisher/revenue?start=2026-05-01&end=2026-05-31" \
| jq .
import os, requests
from decimal import Decimal
API_KEY = os.environ["WEFORADS_API_KEY"]
BASE = "https://api.weforads.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Today's revenue
resp = requests.get(f"{BASE}/publisher/revenue/today", headers=HEADERS, timeout=10)
resp.raise_for_status()
data = resp.json()
rev = Decimal(data["revenue_usd"]) # parse as decimal, not float
imps = data["impressions"]
print(f"Today: ${rev:.2f} on {imps:,} impressions")
# Date range
resp = requests.get(
f"{BASE}/publisher/revenue",
headers=HEADERS,
params={"start": "2026-05-01", "end": "2026-05-31"},
timeout=10,
)
for day in resp.json()["days"]:
print(day["date"], day["revenue_usd"])
// Works in Node 18+ and modern browsers via server-side proxy.
// NEVER ship the bearer token to a browser directly.
const API_KEY = process.env.WEFORADS_API_KEY;
const BASE = "https://api.weforads.com/api/v1";
async function getToday() {
const res = await fetch(`${BASE}/publisher/revenue/today`, {
headers: { "Authorization": `Bearer ${API_KEY}` },
});
if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
return res.json();
}
async function getRange(start, end) {
const qs = new URLSearchParams({ start, end });
const res = await fetch(`${BASE}/publisher/revenue?${qs}`, {
headers: { "Authorization": `Bearer ${API_KEY}` },
});
return res.json();
}
const today = await getToday();
console.log(`Today: $${today.revenue_usd} (${today.impressions.toLocaleString()} imps)`);
Tip: for shell pipelines, pair the API with jq for filtering and datamash or awk for arithmetic. Decimal precision survives both.
When things don't.
Every error response uses the same shape: an HTTP status code plus a JSON body with error (a stable machine-readable slug) and message (human-readable). Log the slug, show the message.
{
"error": "invalid_api_key",
"message": "The API key is missing, malformed, or revoked.",
"request_id": "req_2026060214a8f4"
}
| HTTP | Slug | Cause |
|---|---|---|
| 400 | invalid_dates | start or end is missing, malformed, in the future, or end < start. |
| 400 | range_too_large | Date range exceeds 92 days. Slice into smaller windows. |
| 400 | invalid_site_id | site_id is not an integer, or does not belong to your account. |
| 401 | invalid_api_key | Missing, malformed, or revoked bearer token. Re-issue from the API Access page. |
| 403 | key_disabled | Key was disabled by an account admin. Check the API Access page for status. |
| 404 | not_found | Endpoint path is wrong, or the resource (e.g. specific site) doesn't exist. |
| 429 | rate_limited | Per-minute or per-day window exhausted. See Retry-After header. |
| 500 | internal_error | Something on our side broke. The request_id is the fastest way for us to find it — include it in any support email. |
| 503 | unavailable | Brief upstream outage or maintenance window. Safe to retry with exponential backoff. |
Retry policy
- 4xx errors (except 429) are not retryable. Fix the request.
- 429: wait the number of seconds in
Retry-After, then resume. Do not hammer. - 500 / 503: retry with exponential backoff (e.g. 1s, 2s, 4s, 8s). Give up after 4 attempts and surface the error.
Versions in the URL.
The version is part of the base path (/api/v1). When we publish a backwards-incompatible change we will mint /api/v2 alongside it and run both in parallel for at least 12 months. We will not silently break v1.
What counts as breaking
- Removing a field from a response.
- Changing a field's type (e.g. number → string, decimal-string → float).
- Changing a field's semantic meaning (e.g. UTC date → account-timezone date).
- Changing the shape of the error envelope or renaming an error slug.
- Tightening required parameters or rate limits without a deprecation window.
What does not count as breaking
- Adding new fields to a response (your parser should ignore unknown keys).
- Adding new optional query parameters.
- Adding new endpoints.
- Adding new error slugs for failure modes that previously returned
500.
We announce deprecations via email to the account owner and post them in the changelog below. Subscribe a generic alias (apis@yourdomain) to avoid surprises during team turnover.
What's shipped.
GET /publisher/revenue/today and GET /publisher/revenue live with bearer-token auth, per-key rate limiting (60/min · 1,000/day), and the stable error envelope documented above. Direct Deals revenue is included in sources.direct_deals_usd.
Want to be told when something changes? Make sure your account owner email is current — that is where deprecation notices and security advisories go.
Generate your first key in 20 seconds.
Keys are issued instantly. No approval queue, no sales call.