Skip to main content

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 displayWhat 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.dimensionsnot 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.

Pagination

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>" }
OperatorWhat it does
isExact match (scalar value)
not_isNot equal
inMatch any value in an array
not_inMatch none of the values in an array
containsSubstring match (case-sensitive)
contains_case_insensitiveSubstring match (case-insensitive)
matchesRegex 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

StatusMeaningWhat to check
400Validation errorThe response body’s detail / errors field.
401API key missing or invalidThe X-API-Key header; whether the key has been revoked.
403Key valid but no accessThe category_id may belong to a different org.
404Wrong pathTypo or wrong API version.
429Rate limitedBack off; throttle to ≤600/hr.
5xxServer errorRetry 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()