Integrations
REST API reference
Read scores, issues, alerts, deploys, and trends through authenticated edge endpoints.
Base URL: https://api.flusterduck.com/v1
Authentication
Pass your key in the Authorization header:
Authorization: Bearer fd_sec_your_key_here
fd_sec_ keys are scoped to a single site and work for read endpoints with query:read. Write endpoints require a logged-in user JWT; browser code must never use fd_sec_ keys.
You can also authenticate with a user JWT issued during login. Same header format.
Rate limits
| Surface | Limit |
|---|---|
| Read API | 100 requests/minute per org |
| Write API | 30 requests/minute per org |
Responses over the limit return 429 with a Retry-After header in seconds.
Read endpoints
GET /scores
All tracked pages with their current confusion scores.
curl https://api.flusterduck.com/v1/scores \
-H "Authorization: Bearer fd_sec_xxxx"
{
"scores": [
{
"page": "/checkout",
"score": 72,
"trend": "up",
"top_signal": "rage_click",
"issue_count": 3,
"updated_at": "2026-06-10T14:22:05Z"
},
{
"page": "/pricing",
"score": 41,
"trend": "stable",
"top_signal": "dead_click",
"issue_count": 1,
"updated_at": "2026-06-10T14:21:47Z"
}
]
}
trend is up, down, or stable relative to the 7-day baseline.
GET /page
Full data for one page.
curl "https://api.flusterduck.com/v1/page?page=/checkout" \
-H "Authorization: Bearer fd_sec_xxxx"
{
"page": "/checkout",
"score": 72,
"score_history": [
{ "date": "2026-06-10", "score": 72 },
{ "date": "2026-06-09", "score": 61 },
{ "date": "2026-06-08", "score": 58 }
],
"issues": [
{
"id": "iss_xxxxxxxxxxxx",
"title": "Dead clicks on complete purchase button",
"selector": "button[type='submit']",
"signal_type": "dead_click",
"signal_count": 47,
"severity": 84,
"status": "open"
}
],
"elements": [
{
"selector": "button[type='submit']",
"label": "Complete Purchase",
"signals": { "dead_click": 47, "rage_click": 12 },
"friction_score": 84
}
],
"budget": {
"limit": 50,
"current": 72,
"exceeded": true
}
}
GET /issues
UX issues, filterable by status.
curl "https://api.flusterduck.com/v1/issues?status=open" \
-H "Authorization: Bearer fd_sec_xxxx"
{
"issues": [
{
"id": "iss_xxxxxxxxxxxx",
"title": "Dead clicks on upgrade CTA",
"page": "/pricing",
"selector": "[data-cta='upgrade']",
"signal_type": "dead_click",
"signal_count": 47,
"severity": 72,
"status": "open",
"created_at": "2026-06-10T14:22:05Z"
}
],
"total": 1
}
Status options: open, triaged, in_progress, verified, resolved, regressed
GET /issues/:id
Full detail on one issue.
{
"id": "iss_xxxxxxxxxxxx",
"title": "Dead clicks on upgrade CTA",
"page": "/pricing",
"selector": "[data-cta='upgrade']",
"signal_type": "dead_click",
"signal_count": 47,
"severity": 72,
"status": "open",
"hypothesis": "Element appears clickable but either navigates to an unexpected destination or produces no visible response.",
"sessions": [
{ "id": "ses_xxxxxxxxxxxx", "signals": 8, "timestamp": "2026-06-10T13:41:22Z" }
],
"verifications": [],
"created_at": "2026-06-10T14:22:05Z",
"updated_at": "2026-06-10T14:22:05Z"
}
GET /alerts
curl "https://api.flusterduck.com/v1/alerts?status=open" \
-H "Authorization: Bearer fd_sec_xxxx"
{
"alerts": [
{
"id": "alt_xxxxxxxxxxxx",
"rule_name": "Checkout rage click spike",
"page": "/checkout",
"trigger_type": "spike",
"score_before": 31,
"score_after": 68,
"status": "open",
"triggered_at": "2026-06-10T14:22:05Z"
}
]
}
GET /trends
Confusion score history. days accepts 1-90, defaults to 7.
curl "https://api.flusterduck.com/v1/trends?page=/checkout&days=14" \
-H "Authorization: Bearer fd_sec_xxxx"
{
"page": "/checkout",
"days": 14,
"data": [
{ "date": "2026-06-10", "score": 72 },
{ "date": "2026-06-09", "score": 61 }
]
}
GET /deploys
{
"deploys": [
{
"id": "dep_xxxxxxxxxxxx",
"version": "2026.06.10",
"environment": "production",
"confusion_before": 58,
"confusion_after": 72,
"delta": 14,
"recorded_at": "2026-06-10T09:00:00Z"
}
]
}
confusion_after is null until the scoring engine has enough post-deploy data (typically 5 minutes).
GET /session
Full event timeline for a session.
curl "https://api.flusterduck.com/v1/session?session_id=ses_xxxxxxxxxxxx" \
-H "Authorization: Bearer fd_sec_xxxx"
{
"session_id": "ses_xxxxxxxxxxxx",
"page_count": 4,
"signal_count": 11,
"started_at": "2026-06-10T13:38:10Z",
"events": [
{
"type": "page_view",
"page": "/pricing",
"timestamp": "2026-06-10T13:38:10Z"
},
{
"type": "signal",
"signal": "dead_click",
"selector": "[data-cta='upgrade']",
"page": "/pricing",
"timestamp": "2026-06-10T13:38:44Z"
}
]
}
Other read endpoints
| Endpoint | Returns |
|---|---|
GET /elements?page=/checkout | Element-level friction breakdown |
GET /flows | Page-to-page navigation edges |
GET /compare?a=/pricing&b=/checkout | Side-by-side score comparison |
GET /recommendations | Prioritized fix list |
GET /revenue | Revenue impact estimates (requires conversion tracking) |
GET /raw?table=signals&limit=100 | Raw table rows |
GET /export/events.csv | CSV export of event rows |
GET /mcp/context | Full site context snapshot |
Write endpoints
Write endpoints require Content-Type: application/json and a user JWT.
POST /issues/:id
Update status, add a note, or assign.
curl -X POST https://api.flusterduck.com/v1/issues/iss_xxxxxxxxxxxx \
-H "Authorization: Bearer user_jwt_here" \
-H "Content-Type: application/json" \
-d '{
"status": "triaged",
"note": "Confirmed on Safari iOS 17. Related to the disabled-state border color.",
"assigned_to": "alex"
}'
Status options: open, triaged, in_progress, verified, resolved, ignored
POST /alerts/:id
{
"status": "resolved",
"resolved_reason": "Deploy #4421 fixed the broken CTA selector"
}
Status options: acknowledged, investigating, resolved
POST /annotations
{
"message": "Redesigned checkout flow -- monitoring score"
}
POST /alert-rules
{
"name": "Checkout rage click spike",
"trigger_type": "spike",
"threshold": 25,
"cooldown_minutes": 60,
"channels": ["email", "slack"],
"page_pattern": "/checkout*",
"slack_channel": "#incidents",
"enabled": true
}
See [Alerts](./alerts) for all trigger types and channel options.
PATCH /alert-rules/:id
Partial update. Use "enabled": false to silence a rule without deleting it.
{
"enabled": false
}
DELETE /alert-rules/:id
Permanent. Use PATCH with enabled: false if you might want it back.
Errors
{
"code": "not_found",
"message": "Issue iss_xxxx not found or does not belong to this site"
}
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_input | Missing required field or invalid value |
| 401 | unauthorized | Missing, expired, or malformed key |
| 403 | forbidden | Key doesn't have the required scope |
| 404 | not_found | Resource doesn't exist or belongs to a different site |
| 429 | rate_limited | Slow down. Check Retry-After. |
| 500 | server_error | Something went wrong on our end |