Skip to main content

REST API Reference

The Fond REST API lets you retrieve analytics data and trigger runs programmatically. All endpoints live under /api/v1/.

Authentication

Every request must include an API key. You can create keys from the dashboard at Settings > API Keys (admin/owner role required).

Pass the key in either header:

Authorization: Bearer sk-your-key-here
X-API-Key: sk-your-key-here

Keys are shown once at creation. Store them securely — they cannot be retrieved later.

Error responses

All auth failures return 401:

{ "error": "Invalid API key" }

This applies to missing, invalid, revoked, and expired keys.


Endpoints

List websites

GET /api/v1/websites

Returns all websites with setup status COMPLETE in the organization.

Response

{
"websites": [
{
"id": "clx...",
"url": "https://example.com",
"name": "Example Store",
"createdAt": "2026-01-15T10:30:00.000Z"
}
]
}

List runs

GET /api/v1/websites/:websiteId/runs

Query parameters

ParamTypeDefaultDescription
statusstringFilter by status: PENDING, RUNNING, COMPLETED, COMPLETED_WITH_ERRORS, FAILED
limitinteger20Results per page (1-100)
offsetinteger0Number of results to skip

Response

{
"runs": [
{
"id": "clx...",
"runDate": "2026-03-06T00:00:00.000Z",
"status": "COMPLETED",
"triggeredBy": "MANUAL",
"totalQueries": 24,
"totalResults": 72,
"failedResults": 0,
"locale": "en-US",
"startedAt": "2026-03-06T12:00:00.000Z",
"completedAt": "2026-03-06T12:05:30.000Z",
"createdAt": "2026-03-06T12:00:00.000Z"
}
],
"total": 42,
"limit": 20,
"offset": 0
}

Trigger a run

POST /api/v1/websites/:websiteId/runs

Starts a new query run for the website. Returns 409 if a run is already in progress.

Request body (optional, JSON)

FieldTypeDescription
namestringOptional label for the run (1-100 chars)
queryIdsstring[]Run only specific queries. Omit to run all.

Example

curl -X POST https://your-domain.com/api/v1/websites/clx.../runs \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{"name": "Weekly check"}'

Response 201 Created

{
"id": "clx...",
"status": "RUNNING",
"triggeredBy": "MANUAL",
"totalQueries": 24,
"totalResults": 0,
"failedResults": 0,
"startedAt": "2026-03-06T14:00:00.000Z",
"completedAt": null
}

Error responses

StatusReason
400Invalid body or no valid queries to run
404Website not found or not in your organization
409A run is already in progress

Get run details

GET /api/v1/websites/:websiteId/runs/:runId

Returns a single run with summary statistics.

Response

{
"id": "clx...",
"runDate": "2026-03-06T00:00:00.000Z",
"status": "COMPLETED",
"triggeredBy": "MANUAL",
"totalQueries": 24,
"totalResults": 72,
"failedResults": 0,
"locale": "en-US",
"modelVersions": { "gemini": "gemini-2.0-flash", "openai": "gpt-5-mini" },
"repetitions": 3,
"startedAt": "2026-03-06T12:00:00.000Z",
"completedAt": "2026-03-06T12:05:30.000Z",
"errorMessage": null,
"createdAt": "2026-03-06T12:00:00.000Z",
"summary": {
"totalResults": 72,
"avgVisibilityScore": 34.5,
"mentionCount": 45,
"citationCount": 12
}
}

List results

GET /api/v1/websites/:websiteId/runs/:runId/results

Returns paginated query results for a run.

Query parameters

ParamTypeDefaultDescription
providerstringFilter by provider (e.g. gemini, openai)
queryIdstringFilter by specific query
limitinteger50Results per page (1-100)
offsetinteger0Number of results to skip

Response

{
"results": [
{
"id": "clx...",
"queryId": "clx...",
"provider": "gemini",
"model": "gemini-2.0-flash",
"isMentioned": true,
"isCited": false,
"isLinked": false,
"mentionPosition": 2,
"mentionContext": "...recommended by Example Store...",
"sentiment": "POSITIVE",
"extractedUrls": [],
"citations": null,
"visibilityScore": 45,
"followUpQuestions": ["What products does Example Store offer?"],
"brandInFollowUp": true,
"confidence": 0.85,
"groundingUrls": ["https://example.com/products"],
"errorMessage": null,
"createdAt": "2026-03-06T12:01:00.000Z",
"query": {
"text": "best online stores for electronics",
"queryType": "CATEGORY",
"categoryTier": "SAFETY",
"queryFocus": "PRODUCT"
}
}
],
"total": 72,
"limit": 50,
"offset": 0
}

Trigger an opportunity (WIP)

Internal/WIP — not yet stable. Used by the GEO Content Engine to ingest external opportunity triggers (e.g. matched social posts) and run the content pipeline against them. Subject to change; not recommended for customer integration yet.

POST /api/v1/opportunities

Authenticated with the same API key. Body must include websiteId, url, platform, and context; matchedKeywords is optional (defaults to []). Returns 202 { id } once the trigger is queued; the content pipeline runs asynchronously.


Pagination

All list endpoints support limit and offset parameters and return a total count. To iterate through all results:

# Page 1
GET /api/v1/websites/:id/runs?limit=20&offset=0

# Page 2
GET /api/v1/websites/:id/runs?limit=20&offset=20

Rate limits

There are currently no enforced rate limits on the API. Be considerate with request frequency — avoid tight polling loops when checking run status.


Common workflows

Poll for run completion

# 1. Trigger a run
RUN_ID=$(curl -s -X POST .../runs -H "Authorization: Bearer $KEY" | jq -r .id)

# 2. Poll status every 30 seconds
while true; do
STATUS=$(curl -s .../runs/$RUN_ID -H "Authorization: Bearer $KEY" | jq -r .status)
echo "Status: $STATUS"
[ "$STATUS" = "COMPLETED" ] || [ "$STATUS" = "FAILED" ] && break
sleep 30
done

# 3. Fetch results
curl -s .../runs/$RUN_ID/results -H "Authorization: Bearer $KEY" | jq .

Export all results for a website

OFFSET=0
while true; do
RESPONSE=$(curl -s ".../runs/$RUN_ID/results?limit=100&offset=$OFFSET" \
-H "Authorization: Bearer $KEY")
echo "$RESPONSE" >> results.json
TOTAL=$(echo "$RESPONSE" | jq .total)
OFFSET=$((OFFSET + 100))
[ $OFFSET -ge $TOTAL ] && break
done