Documentation Index
Fetch the complete documentation index at: https://docs.tryprofound.com/llms.txt
Use this file to discover all available pages before exploring further.
Authentication
Every request needs your API key in the X-API-Key header. The Python
SDK reads it from the PROFOUND_API_KEY env var.
import os
from profound import Profound
client = Profound(api_key=os.environ["PROFOUND_API_KEY"])
categories = client.organizations.categories.list()
Generate a key in the app under Settings → API Keys. Treat it like a
password — it has full read access to your org’s analytics data.
Rate limit
600 requests per hour, per key. Anything above returns 429 Too Many Requests. Cache responses where you can and batch period-over-period or
multi-asset queries instead of fanning out.
end_date is exclusive — add one day
end_date is parsed at the start of that day in Eastern Time,
so it’s excluded from the response. To include all of May 10, send
end_date="2026-05-11".
| Window you want to display | What to send |
|---|
May 4 → May 10 (7 days, inclusive) | start_date="2026-05-04", end_date="2026-05-11" |
April 1 → April 30 (full month) | start_date="2026-04-01", end_date="2026-05-01" |
The date_interval buckets ("day" / "week" / "month") are also
computed in ET.
Read column positions from info.query, not your request
Each row in the response packs its metrics and dimensions as arrays.
The order of values in those arrays comes from info.query.metrics and
info.query.dimensions — not from the order you sent in the
request. Always look it up:
order = res.info.query["metrics"]
i_score = order.index("visibility_score")
score = res.data[0].metrics[i_score]
A response always looks like this:
{
"info": {
"total_rows": 12345,
"query": {
"metrics": ["visibility_score", "share_of_voice"],
"dimensions": ["asset_name"]
}
},
"data": [
{ "metrics": [0.42, 0.17], "dimensions": ["<your-asset>"] }
]
}
Period-over-period deltas are client-side
The API doesn’t return change vs the previous period. Run the same call
twice — current window and a prior window of equal length — and subtract.
Don’t average daily rows to get a period score
A call with dimensions=["date"] returns one row per day. A call without
date returns one row for the whole window. These are different
numbers: the period score is traffic-weighted, an average of daily
rows is not. Use the no-date call for headlines; use the with-date
call for charts. Never derive one from the other.
Default pagination.limit is 100. Max is 50,000. Use
info.total_rows (returned on every response) to decide whether to
paginate. Almost all queries fit in a single 50k page; only heavy
dimensions=["url", ...] citation queries usually need a second page.
pagination={"limit": 50000, "offset": 0}
If you do need more, increment offset by limit until you’ve covered
total_rows.
Filters
Every report endpoint accepts a filters array of {field, operator, value} objects:
{ "field": "asset_name", "operator": "is", "value": "<your-asset-name>" }
| Operator | What it does |
|---|
is | Exact match (scalar value) |
not_is | Not equal |
in | Match any value in an array |
not_in | Match none of the values in an array |
contains | Substring match (case-sensitive) |
contains_case_insensitive | Substring match (case-insensitive) |
matches | Regex match |
prompt_type (with values like "visibility") maps to the app’s view
toggles. Send prompt_type=visibility on Citations / Visibility queries
to mirror the default UI scope.
Error responses
| Status | Meaning | What to check |
|---|
400 | Validation error | The response body’s detail / errors field. |
401 | API key missing or invalid | The X-API-Key header; whether the key has been revoked. |
403 | Key valid but no access | The category_id may belong to a different org. |
404 | Wrong path | Typo or wrong API version. |
429 | Rate limited | Back off; throttle to ≤600/hr. |
5xx | Server error | Retry with exponential backoff. |
Timezones
All bucketing happens in Eastern Time. A “last 7 days” range anchored
to your local clock can land on a different ET day than you expect.
Anchor scheduled jobs to ET:
from datetime import datetime
from zoneinfo import ZoneInfo
today_et = datetime.now(ZoneInfo("America/New_York")).date()