Pool, Results and Pages
Suggestions waiting on a decision, the results that say what the work changed, and the page registry underneath both.
Code example
curl -X GET 'https://api.trakkr.ai/get-opportunity-pool?brand_id=00000000-0000-4000-8000-81f286d10c3c&family=fix&limit=20' \
-H "Authorization: Bearer $TRAKKR_API_KEY"{
"opportunities": [
{
"id": "e1f2a3b4-...",
"kind": "audit_fix",
"family": "fix",
"title": "Add a meta description to /help/returns",
"impact": "high",
"status": "new",
"evidence": [
{
"kind": "signal",
"source": "site_audit",
"observed_at": "2026-07-28T06:00:00Z",
"payload": { "label": "Missing since", "value": "12 days" },
"deep_link": "https://app.trakkr.ai/optimize"
}
],
"page_id": "9a8b...",
"page_url": "https://example.com/help/returns",
"dedup_key": "audit_fix:meta_description:https://example.com/help/returns",
"expires_at": "2026-08-18T06:00:00Z",
"created_at": "2026-07-28T06:00:00Z"
}
],
"meta": { "total": 137, "limit": 20, "next_cursor": "o:20" }
}Synthetic example. Values are made up.
curl -X POST 'https://api.trakkr.ai/commit-opportunity?brand_id=00000000-0000-4000-8000-81f286d10c3c' \
-H "Authorization: Bearer $TRAKKR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"opportunity_id":"11111111-1111-4111-8111-111111111111","action":"commit"}'{
"status": "committed",
"opportunity_id": "e1f2a3b4-...",
"action_id": "a1b2c3d4-...",
"measurement_plan": {
"subject": { "page_id": "9a8b7c6d-..." },
"primary": { "metric": "bot_fetches", "source": "crawler_logs" },
"secondary": [],
"window_days": 14,
"moved_if": "after >= max(before*1.25, before+min_gain)",
"harm_if": "after <= before*0.75",
"rollback_ref": null
}
}Synthetic example. Values are made up.
curl -X GET 'https://api.trakkr.ai/get-results?brand_id=00000000-0000-4000-8000-81f286d10c3c&verdict=earned&limit=20' \
-H "Authorization: Bearer $TRAKKR_API_KEY"{
"results": [
{
"id": "r1...",
"action_id": "a1...",
"verdict": "earned",
"family": "fix",
"summary": "Citations went from 2 to 6 over 14 days.",
"primary_metric": {
"label": "Citations",
"before": 2,
"after": 6,
"source": "citations"
},
"window_days": 14,
"reason": null,
"measured_at": "2026-07-28T06:00:00Z",
"rolled_back": false,
"page_url": "https://example.com/help/returns"
}
],
"meta": { "total": 41, "limit": 20, "next_cursor": null },
"verdict_counts": {
"earned": 12,
"no_change": 9,
"couldnt_measure": 20
}
}Synthetic example. Values are made up.
curl -X GET 'https://api.trakkr.ai/get-pages?brand_id=00000000-0000-4000-8000-81f286d10c3c&ownership=owned&limit=50' \
-H "Authorization: Bearer $TRAKKR_API_KEY"{
"pages": [
{
"id": "9a8b7c6d-...",
"url": "https://example.com/help/returns",
"slug": "/help/returns",
"ownership": "owned",
"title": "Returns and refunds",
"tracked": true,
"bottleneck": "reached",
"verdict": "This page is available but AI crawlers aren't fetching it.",
"last_seen_at": "2026-07-28T06:00:00Z"
}
],
"meta": { "total": 812, "limit": 50, "next_cursor": "o:50" }
}Synthetic example. Values are made up.
API key required
60 req/min reads, 30 req/min writesHow the three fit together
Every recommendation Trakkr makes lands in one pool. A person or an agent decides what to do with each one, and committing freezes how that change will be measured. When the window closes, the pipeline measures it and writes a result. Pages are what all of it hangs on: one row per URL, keyed the same way everywhere.
/get-opportunity-pool is not the same thing as /get-opportunities. The older endpoint lists citation outreach targets and keeps working exactly as it does today. This one is the unified pool that every recommendation system writes to.Pool
/get-opportunity-poolAuthenticate with Authorization: Bearer $TRAKKR_API_KEY. Key and access guide
Request parameters
brand_idstringqueryRequired
Brand UUID
Format: uuid
familystring | nullqueryOptional
fix|refresh|create|earn|discuss|optimize|setup|play
kindstring | nullqueryOptional
Comma-separated kinds
impactstring | nullqueryOptional
low|medium|high
limitintegerqueryOptional
Default: 50 · Minimum: 1 · Maximum: 200
cursorstring | nullqueryOptional
Opaque cursor from meta.next_cursor
Responses and errors
200Successful Response
application/json
opportunitiesobject[]Required
Fields in opportunities
Array of object.
idstringRequired
kindstringRequired
familystringRequired
titlestringRequired
impactstringRequired
statusstringRequired
evidenceobject[]Optional
Default: []
Fields in evidence
Array of object. Default: []
object. Additional keys are allowed.
page_idstring | nullOptional
page_urlstring | nullOptional
dedup_keystring | nullOptional
expires_atstring | nullOptional
created_atstringRequired
metaobjectRequired
Cursor pagination (07 §11 rule 6). Never mixed with PaginationInfo.
Fields in meta
totalintegerRequired
limitintegerRequired
next_cursorstring | nullOptional
401Missing, malformed, or expired credentials
application/json
detailstring | object | nullOptional
errorstring | nullOptional
402The current plan does not fund this operation
application/json
detailstring | object | nullOptional
errorstring | nullOptional
403Invalid credentials, missing brand access, or insufficient role
application/json
detailstring | object | nullOptional
errorstring | nullOptional
422Validation Error
application/json
detailobject[]Optional
Fields in detail
Array of object.
locstring | integer[]Required
Fields in loc
Array of string | integer.
string
string.
integer
integer.
msgstringRequired
typestringRequired
429Rate limit exceeded
application/json
detailstring | object | nullOptional
errorstring | nullOptional
500The operation failed
application/json
detailstring | object | nullOptional
errorstring | nullOptional
503Authentication, entitlement, or a required dependency is temporarily unavailable
application/json
detailstring | object | nullOptional
errorstring | nullOptional
Suggestions waiting on a decision. The order is deterministic: impact bands first, then each family's freshest items in turn, so one high-volume source cannot crowd out everything else. Nothing here is committed work yet.
Only two statuses ever come back: new and seen. Anything committed, dismissed, snoozed or expired has left the pool, so there is no filter that brings those rows back here.
Commit, dismiss or snooze
/commit-opportunityAuthenticate with Authorization: Bearer $TRAKKR_API_KEY. Key and access guide
Request parameters
brand_idstringqueryRequired
Brand UUID
Format: uuid
JSON request body (required)
opportunity_idstringRequired
Format: uuid
actionstringOptional
Default: "commit"
reasonstring | nullOptional
snooze_daysinteger | nullOptional
Minimum: 1 · Maximum: 90
Responses and errors
200Successful Response
application/json
statusstringRequired
opportunity_idstringRequired
action_idstring | nullOptional
measurement_planobject | nullOptional
401Missing, malformed, or expired credentials
application/json
detailstring | object | nullOptional
errorstring | nullOptional
402The current plan does not fund this operation
application/json
detailstring | object | nullOptional
errorstring | nullOptional
403Invalid credentials, missing brand access, or insufficient role
application/json
detailstring | object | nullOptional
errorstring | nullOptional
422Validation Error
application/json
detailobject[]Optional
Fields in detail
Array of object.
locstring | integer[]Required
Fields in loc
Array of string | integer.
string
string.
integer
integer.
msgstringRequired
typestringRequired
429Rate limit exceeded
application/json
detailstring | object | nullOptional
errorstring | nullOptional
500The operation failed
application/json
detailstring | object | nullOptional
errorstring | nullOptional
503Authentication, entitlement, or a required dependency is temporarily unavailable
application/json
detailstring | object | nullOptional
errorstring | nullOptional
One explicit verb per call. Committing creates tracked work and freezes its measurement plan, which is what makes a before/after result possible later. Dismissing takes a reason. Snoozing hides a suggestion and brings it back.
A commit returns status, opportunity_id, action_id and the frozen measurement_plan. Dismiss and snooze return only status and opportunity_id. Setup work is never measured, so its plan comes back null, and committing something already committed returns already_accepted with the existing action id.
snooze_days is validated, not just documented: anything outside 1-90 is rejected with 422 before the call runs. An action other than commit, dismiss or snooze returns 400, and an opportunity_id that belongs to another brand returns 404.Results
/get-resultsAuthenticate with Authorization: Bearer $TRAKKR_API_KEY. Key and access guide
Request parameters
brand_idstringqueryRequired
Brand UUID
Format: uuid
verdictstring | nullqueryOptional
earned|no_change|harm|couldnt_measure
familystring | nullqueryOptional
daysinteger | nullqueryOptional
Only Results from the last N days
Minimum: 1 · Maximum: 3650
limitintegerqueryOptional
Default: 50 · Minimum: 1 · Maximum: 200
cursorstring | nullqueryOptional
Responses and errors
200Successful Response
application/json
resultsobject[]Required
Fields in results
Array of object.
idstringRequired
action_idstringRequired
verdictstringRequired
familystringRequired
summarystringRequired
primary_metricobject | nullOptional
window_daysinteger | nullOptional
reasonstring | nullOptional
measured_atstringRequired
rolled_backbooleanOptional
Default: false
page_urlstring | nullOptional
metaobjectRequired
Cursor pagination (07 §11 rule 6). Never mixed with PaginationInfo.
Fields in meta
totalintegerRequired
limitintegerRequired
next_cursorstring | nullOptional
verdict_countsobjectRequired
401Missing, malformed, or expired credentials
application/json
detailstring | object | nullOptional
errorstring | nullOptional
402The current plan does not fund this operation
application/json
detailstring | object | nullOptional
errorstring | nullOptional
403Invalid credentials, missing brand access, or insufficient role
application/json
detailstring | object | nullOptional
errorstring | nullOptional
422Validation Error
application/json
detailobject[]Optional
Fields in detail
Array of object.
locstring | integer[]Required
Fields in loc
Array of string | integer.
string
string.
integer
integer.
msgstringRequired
typestringRequired
429Rate limit exceeded
application/json
detailstring | object | nullOptional
errorstring | nullOptional
500The operation failed
application/json
detailstring | object | nullOptional
errorstring | nullOptional
503Authentication, entitlement, or a required dependency is temporarily unavailable
application/json
detailstring | object | nullOptional
errorstring | nullOptional
What completed work actually changed. Every row is computed by Trakkr's pipeline from real before/after data over the window that was frozen at commit time. Nothing here is self-reported.
A result never claims causation. It says a metric moved during the window, which is a different and more honest statement. Four verdicts are possible: earned (the metric cleared the bar the plan set), no_change (it held steady), harm (it fell, reported as "coincided with a drop"), and couldnt_measure (the data needed was not there, and the reason says which).
/get-proof remains available as a deprecated compatibility alias for existing integrations.Pages
/get-pagesAuthenticate with Authorization: Bearer $TRAKKR_API_KEY. Key and access guide
Request parameters
brand_idstringqueryRequired
Brand UUID
Format: uuid
ownershipstring | nullqueryOptional
owned|competitor|editorial|social|video|other
trackedboolean | nullqueryOptional
limitintegerqueryOptional
Default: 50 · Minimum: 1 · Maximum: 200
cursorstring | nullqueryOptional
Responses and errors
200Successful Response
application/json
pagesobject[]Required
Fields in pages
Array of object.
idstringRequired
urlstringRequired
slugstringRequired
ownershipstringRequired
titlestring | nullOptional
trackedbooleanOptional
Default: false
bottleneckstring | nullOptional
verdictstring | nullOptional
last_seen_atstring | nullOptional
metaobjectRequired
Cursor pagination (07 §11 rule 6). Never mixed with PaginationInfo.
Fields in meta
totalintegerRequired
limitintegerRequired
next_cursorstring | nullOptional
401Missing, malformed, or expired credentials
application/json
detailstring | object | nullOptional
errorstring | nullOptional
402The current plan does not fund this operation
application/json
detailstring | object | nullOptional
errorstring | nullOptional
403Invalid credentials, missing brand access, or insufficient role
application/json
detailstring | object | nullOptional
errorstring | nullOptional
422Validation Error
application/json
detailobject[]Optional
Fields in detail
Array of object.
locstring | integer[]Required
Fields in loc
Array of string | integer.
string
string.
integer
integer.
msgstringRequired
typestringRequired
429Rate limit exceeded
application/json
detailstring | object | nullOptional
errorstring | nullOptional
500The operation failed
application/json
detailstring | object | nullOptional
errorstring | nullOptional
503Authentication, entitlement, or a required dependency is temporarily unavailable
application/json
detailstring | object | nullOptional
errorstring | nullOptional
One row per URL the brand owns or appears on. Because everything is keyed on one canonical URL form, a page here is the same page across crawler data, citations, audits and search. Rows carry the page's bottleneck, meaning the first stage of its funnel that is stuck.
Rows come back newest last_seen_at first. Each one carries id, url, slug, ownership, title, tracked, bottleneck, verdict and last_seen_at. The bottleneck is a funnel stage name (available, reached, understood, relevant, selected or visited) and the verdict is the plain sentence that goes with it. Both are null until there is enough data to name one.
Pagination
These endpoints use cursors, not offsets. Read meta.next_cursor and pass it back as cursor to get the next page. A null cursor means you have reached the end. Older endpoints such as /get-actions keep their existing offset pagination unchanged.
