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

SurfaceLimit
Read API100 requests/minute per org
Write API30 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"
    }
  ]
}

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

EndpointReturns
GET /elements?page=/checkoutElement-level friction breakdown
GET /flowsPage-to-page navigation edges
GET /compare?a=/pricing&b=/checkoutSide-by-side score comparison
GET /recommendationsPrioritized fix list
GET /revenueRevenue impact estimates (requires conversion tracking)
GET /raw?table=signals&limit=100Raw table rows
GET /export/events.csvCSV export of event rows
GET /mcp/contextFull 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"
}
StatusCodeMeaning
400invalid_inputMissing required field or invalid value
401unauthorizedMissing, expired, or malformed key
403forbiddenKey doesn't have the required scope
404not_foundResource doesn't exist or belongs to a different site
429rate_limitedSlow down. Check Retry-After.
500server_errorSomething went wrong on our end