API Reference
Parrad is change control and observability for the GTM stack. The API lets you read the same signal as the dashboard and act on it: vendor changes, the chain each one follows, and the parry written for your flows. Signed webhooks send alerts to your systems as they happen. Responses are JSON unless a route says otherwise.
Base URL: https://parrad.app/api/v1
Authentication
Every request must include your workspace API key as the X-PARRAD-Key header. You can find your key in the dashboard under Settings → API Keys.
PARRAD_API_KEY) in production.
X-PARRAD-Key: prd-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxResponse format
Successful responses are always HTTP 200 (or 201 for created resources, 202 for async jobs) with this envelope:
{
"ok": true,
"data": { /* endpoint-specific payload */ },
"meta": {
"api_version": "1.0",
"timestamp": "2026-10-17T14:32:00.000Z"
}
}Errors
| HTTP code | Meaning |
|---|---|
| 401 | Missing or invalid X-PARRAD-Key |
| 400 | Bad request — missing required field, invalid JSON |
| 404 | Resource or endpoint not found |
| 409 | Conflict — API already exists |
| 500 | Internal server error |
{
"ok": false,
"error": "API not found",
"code": 404
}GET /apis
Returns all monitored APIs with their current risk and capability scores.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis
{
"ok": true,
"data": {
"apis": [
{
"id": "hubspot-crm-v3",
"name": "HubSpot CRM API v3",
"vendor": "HubSpot",
"criticality": "critical",
"version": "v3",
"active": true,
"risk_score": 42,
"risk_level": "yellow",
"opp_score": 28,
"opp_level": "low",
"last_run": "2026-10-17T06:00:00.000Z"
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}GET /apis/{id}
Returns a single API's configuration and full state blob.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/hubspot-crm-v3
{
"ok": true,
"data": {
"api": {
"id": "hubspot-crm-v3", "name": "HubSpot CRM API v3",
"vendor": "HubSpot", "criticality": "critical",
"changelog_url": "https://developers.hubspot.com/changelog",
"state": {
"risk_score": 42,
"risk_level": "yellow",
"last_run": "2026-10-17T06:00:00.000Z"
}
}
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}GET /apis/{id}/risk
Returns the full risk picture for a single API: score, level, confirmed signals, velocity, and score history.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/hubspot-crm-v3/risk
{
"ok": true,
"data": {
"api_id": "hubspot-crm-v3",
"score": 42,
"level": "yellow",
"velocity_multiplier": 1.3,
"velocity_avg_days": 18,
"signals": [
{
"signal_id": "deprecation_announced",
"layer": 1,
"detected_at": "2026-10-10T09:00:00.000Z",
"excerpt": "v1 Lists API sunset extended to April 30, 2027",
"source_url": "https://developers.hubspot.com/changelog/..."
}
],
"score_history": [
{ "date": "2026-10-16", "score": 38 },
{ "date": "2026-10-17", "score": 42 }
],
"last_run": "2026-10-17T06:00:00.000Z"
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}GET /apis/{id}/opp
Returns capability signals for a single API: score, velocity, and signal list.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/hubspot-crm-v3/opp
{
"ok": true,
"data": {
"api_id": "hubspot-crm-v3",
"score": 55,
"level": "medium",
"velocity_label": "active",
"velocity_rate": 2.1,
"signals": [
{
"signal_id": "new_integration_announced",
"excerpt": "HubSpot launches native AI workflow builder",
"source_url": "https://..."
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}POST /apis
Add a new API to your watchlist. Accepts either a domain (auto-generates id, name, vendor) or a fully specified config object with an explicit id.
| Field | Type | Description |
|---|---|---|
| domain | string | Root domain, e.g. stripe.com. Required if id not provided. |
| id | string | Explicit slug, e.g. stripe-api. Required if domain not provided. |
| name | string | Human-readable name. Auto-derived from domain if omitted. |
| vendor | string | Vendor name. Auto-derived from domain if omitted. |
| criticality | string | critical | high | medium. Default: high. |
| changelog_url | string | Optional. URL of the API changelog. |
| github_repo | string | Optional. e.g. stripe/stripe-node. |
| npm_package | string | Optional. e.g. stripe. |
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "stripe.com", "criticality": "critical" }' \ https://parrad.app/api/v1/apis
{
"ok": true,
"data": {
"api": {
"id": "stripe", "name": "Stripe API",
"vendor": "Stripe", "criticality": "critical",
"active": true, "risk_score": null, "last_run": null
}
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}DELETE /apis/{id}
Removes an API from the watchlist and deletes its local state file. Permanent. The response includes type indicating which storage path was used. For bundle APIs (shipped in config), config.active is set to false in Supabase before the record is removed. For user-added APIs, the row is hard-deleted.
curl -X DELETE -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/stripe
{
"ok": true,
"data": {
"deleted": "stripe",
"type": "user-added" // "bundle" | "user-added"
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}GET /alerts
Returns the full alert feed. Alerts are logged automatically when a risk score crosses a threshold band between runs (Green / Yellow / Orange / Red at 20 / 50 / 75).
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/alerts
{
"ok": true,
"data": {
"total": 12,
"unread": 3,
"alerts": [
{
"id": "m8k2a9xz",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"previous_level": "yellow",
"new_level": "orange",
"score": 52,
"timestamp": "2026-10-17T06:01:00.000Z",
"read": false,
"top_signal": "deprecation_announced"
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}GET /summary
Global workspace overview: API count, average scores, level distribution, the 3 most-at-risk APIs, and the 5 most recent alerts.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/summary
{
"ok": true,
"data": {
"total_apis": 8,
"avg_risk_score": 31,
"avg_opp_score": 44,
"level_counts": { "green": 4, "yellow": 2, "orange": 1, "red": 1 },
"unread_alerts": 3,
"most_at_risk": [
{ "api_id": "hubspot-crm-v3", "score": 78, "level": "red" },
{ "api_id": "lemlist-v2", "score": 52, "level": "orange" },
{ "api_id": "stripe", "score": 38, "level": "yellow" }
],
"latest_alerts": [ /* same shape as GET /alerts */ ]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-17T14:32:00.000Z" }
}POST /runs
Triggers a scan for 1–10 APIs asynchronously. Returns a run_id immediately (HTTP 202). api_ids is required — full-stack scans run via cron only. Optional scope controls what is collected: "risk", "opp", or "both" (default). Run records are shared with the UI trigger path — both write to the same runs/{run_id}.json store.
| Field | Type | Description |
|---|---|---|
| run_id | string | Unique identifier, format runs-{epoch_ms} |
| status | string | queued → running → done | error | cancelled | expired |
| started_at | string | ISO timestamp |
| finished_at | string | null | ISO timestamp, null until terminal state |
| created_at | string | ISO timestamp the run was created. Use this for elapsed time, not started_at. |
| trigger_source | string | "api_v1" | "ui" | "cron" |
| api_ids | string[] | APIs included in this run |
| scope | string | "risk" | "opp" | "both" |
| error | string | null | Error message, only set when status = "error" |
| _reused | bool | Present and true when a live identical scan was reused (dedup window: 15 s) |
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "api_ids": ["hubspot-crm-v3", "stripe-api"], "scope": "risk" }' \ https://parrad.app/api/v1/runs
{
"ok": true,
"data": {
"run_id": "runs-1792749600000",
"status": "queued",
"started_at": "2026-10-23T10:00:00.000Z",
"finished_at": null,
"created_at": "2026-10-23T10:00:00.000Z",
"trigger_source": "api_v1",
"api_ids": ["hubspot-crm-v3", "stripe-api"],
"scope": "risk",
"error": null
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}{
"ok": false,
"error": "{"error":"api_ids_required","detail":"Specify between 1 and 10 api_ids. Full-stack scans run via cron only."}",
"code": 400
}Idempotency-Key header (any string) to deduplicate rapid triggers within a 60-second window. Separately, if a single-API scan is already running with a heartbeat <15 s old, the response includes _reused: true and returns the existing run.GET /runs/{run_id}
Poll run status. Status transitions: queued → running → done | error | cancelled | expired. A run still marked running past the scan budget is returned as "expired" (the worker died without writing its end). This is derived from created_at at read time, never stored. When done, the response merges current state for each scanned API.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/runs/runs-1792749600000
{
"ok": true,
"data": {
"run_id": "runs-1792749600000",
"status": "running",
"started_at": "2026-10-23T10:00:00.000Z",
"finished_at": null,
"created_at": "2026-10-23T10:00:00.000Z",
"trigger_source": "api_v1",
"api_ids": ["hubspot-crm-v3"],
"scope": "risk",
"error": null
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:01:20.000Z" }
}{
"ok": true,
"data": {
"run_id": "runs-1792749600000",
"status": "done",
"started_at": "2026-10-23T10:00:00.000Z",
"finished_at": "2026-10-23T10:02:30.000Z",
"created_at": "2026-10-23T10:00:00.000Z",
"trigger_source": "api_v1",
"scope": "risk",
"apis": [
{
"api_id": "hubspot-crm-v3",
"name": "HubSpot CRM API v3",
"risk_score": 42,
"risk_level": "yellow",
"opp_score": 18,
"opp_level": "low",
"signals": [/* sigSlice — see GET /signals for shape */],
"opp_signals": [],
"last_run": "2026-10-23T10:02:00.000Z"
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:02:35.000Z" }
}apis[] reflects the current merged state, not a point-in-time snapshot. ?at= is reserved for future historical snapshots.GET /apis/{id}/sources
Manage custom signal sources attached to a specific API. Sources are URL probes (RSS feeds, HTML changelogs, GitHub repos, npm packages) that the scanner reads on each run. Source type is auto-detected from the URL at write time.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/cargo-api/sources
{
"ok": true,
"data": {
"sources": [
{
"id": "rss-a1b2c3",
"type": "rss",
"url": "https://docs.getcargo.ai/changelog.rss",
"config": {
"discovery_validated": true,
"discovery_confidence": 0.97
}
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}Add a source
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "rss", "url": "https://docs.getcargo.ai/changelog.rss" }' \ https://parrad.app/api/v1/apis/cargo-api/sources
{
"ok": true,
"data": { "sources": [ /* updated sources array */ ] },
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}type (e.g., passing type: "rss" for an HTML page). Returns 409 if the source already exists.Edit a source
curl -X PATCH -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://docs.getcargo.ai/feed.rss" }' \ https://parrad.app/api/v1/apis/cargo-api/sources/rss-a1b2c3
{ "ok": true, "data": { "sources": [ /* updated */ ] }, "meta": { /* ... */ } }Remove a source
curl -X DELETE -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/cargo-api/sources/rss-a1b2c3
{
"ok": true,
"data": {
"deleted": "rss-a1b2c3",
"sources": [ /* remaining active sources */ ]
},
"meta": { /* ... */ }
}deleted_at on the record. The scanner skips soft-deleted sources; historical signals already collected are preserved.GET /apis/{id}/downstream
Returns the dependency footprint of a single API: which APIs depend on it (direct and indirect), which APIs it depends on, and all associated risk/capability impacts.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/apis/linkedin-api/downstream
{
"ok": true,
"data": {
"api_id": "linkedin-api",
"name": "LinkedIn API",
"direct_dependents": [{ "api_id": "cargo-api", "name": "Cargo API" }],
"indirect_dependents": [],
"depends_on": [],
"risk_impacts": [ /* impact objects — see GET /downstream/risks */ ],
"capability_impacts": [],
"edges": [ /* edge objects — see GET /downstream */ ]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /signals
Returns all collected risk signals across your stack. Primary source is the collected_signals Supabase table (persisted across cold starts); falls back to in-memory state files when the table is empty. Signals are sorted newest-first.
| Query param | Description |
|---|---|
| level | Filter by API risk level: green | yellow | orange | red |
| layer | Filter by signal layer: 1–4 |
| signal_id | Exact signal identifier, e.g. deprecation_announced |
| api_name | Case-insensitive substring match on API name |
| since | ISO date — only signals detected after this date |
| until | ISO date — only signals detected before this date |
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ "https://parrad.app/api/v1/signals?level=orange"
{
"ok": true,
"data": {
"signals": [
{
"signal_id": "deprecation_announced",
"layer": 1,
"weight": 5,
"excerpt": "v1 Lists API sunset extended to April 30, 2027",
"source_url": "https://developers.hubspot.com/changelog/...",
"signal_date": "2026-10-10T09:00:00.000Z",
"detected_at": "2026-10-10T09:00:00.000Z",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"vendor": "HubSpot",
"level": "orange"
}
],
"total": 1
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /alerts/unread
Shortcut for unread alerts only. Returns the same alert shape as GET /alerts but pre-filtered to read: false and returned as a flat array (not paginated). Enriched with situation, action, and urgency from AI insights.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/alerts/unread
{
"ok": true,
"data": [
{
"id": "m8k2a9xz",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"previous_level": "yellow",
"new_level": "orange",
"score": 52,
"timestamp": "2026-10-17T06:01:00.000Z",
"read": false,
"situation": "HubSpot v1 Lists API sunset announced for April 2027.",
"action": "Migrate to v3 Lists API before Q1 2027.",
"urgency": "medium",
"changelog_url": "https://developers.hubspot.com/changelog",
"docs_url": null
}
],
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /categories
Returns all API categories with their tool counts. Includes categories defined in app_config even if no APIs are currently assigned to them (count: 0).
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/categories
{
"ok": true,
"data": {
"categories": [
{ "name": "AI", "count": 3 },
{ "name": "CRM", "count": 1 },
{ "name": "Data pipeline", "count": 0 }
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}count: 0 entries are categories configured in app_config with no tools currently assigned. They appear so callers can render a complete category filter UI.GET /downstream
Returns the full dependency graph: raw edges (directed, confidence-scored) and all propagation impacts. Filterable by source, target, confidence tier, dependency type, and date range.
| Query param | Description |
|---|---|
| src | Filter edges by source API id |
| target | Filter edges by target (affected) API id |
| confidence_tier | high (≥0.8) | medium | low |
| dependency_type | e.g. integration, data, auth |
| since / until | ISO date — filter by discovered_at |
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream
{
"ok": true,
"data": {
"total_edges": 56,
"last_run": "2026-10-29T10:40:22.000Z",
"edges": [
{
"source_api_id": "cargo-api",
"affected_api_id":"github-api",
"dependency_type":"integration",
"confidence": 1,
"urgency": "medium",
"evidence": "Cargo workspace: 1 workflow via github connector",
"discovered_at": "2026-10-29T10:40:22.000Z",
"last_checked": "2026-10-29T10:40:22.000Z"
}
],
"impacts": [ /* see GET /downstream/risks for shape */ ]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /downstream/risks
Risk propagation impacts only. Each impact describes how a risk signal in one API cascades to a dependent API. The prop_id field (root_api:dependent_api:kind) is used to call the /actions/fix endpoint.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/risks
{
"ok": true,
"data": {
"total": 6,
"last_run": "2026-10-29T10:40:22.000Z",
"impacts": [
{
"kind": "risk",
"root_api": "linkedin-api",
"root_api_name":"LinkedIn API",
"dependent_api":"cargo-api",
"urgency": "high",
"impact": "Breaking field change in LinkedIn enrichProfile response",
"evidence": "hasDarkUgc field removed from the response: 3 Cargo workflows read it",
"generated_at": "2026-10-29T10:40:22.000Z",
"keys_needed": ["CARGO_API_KEY"],
"keys_present": ["CARGO_API_KEY"],
"keys_missing": []
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}prop_id path param for action endpoints as {root_api}:{dependent_api}:{kind}, e.g. linkedin-api:cargo-api:risk.GET /downstream/capabilities
Capability propagation impacts — same shape as GET /downstream/risks but kind: "opportunity" (enum rename in progress; use /opportunities as an alias URL during transition). Use prop_id to call /actions/build.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/capabilities
{ "ok": true, "data": { "impacts": [ /* same shape as /risks, kind="opportunity" */ ], "total": 2, "last_run": "..." }, "meta": { /* ... */ } }GET /downstream/graph
Full dependency graph with risk levels attached to each node. Useful for building visual graph UIs.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/graph
{
"ok": true,
"data": {
"last_run": "2026-10-29T10:40:22.000Z",
"nodes": [
{
"id": "linkedin-api",
"name": "LinkedIn API",
"vendor": "LinkedIn",
"risk_level": "orange",
"risk_score": 62,
"opp_level": null
}
],
"edges": [ /* same shape as GET /downstream */ ],
"impacts": [ /* same shape as GET /downstream/risks */ ]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}POST /downstream/risks/{prop_id}/actions/fix
Plans the parry for a risk propagation: step by step, for the orchestrator that runs the flow. First call streams a text/event-stream response as the plan is built by Claude. Subsequent calls within the cache TTL return a plain JSON response with from_cache: true — no streaming.
prop_id format: {root_api}:{dependent_api}:{kind} — URL-encode the colons if needed.
curl -sN -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/risks/linkedin-api:cargo-api:risk/actions/fix
Content-Type: text/event-stream data: {"type":"token","text":"plan text tokens..."} data: {"type":"token","text":"...more tokens..."} ... more token events ... data: { "type": "done", "plan": { "situation": "...", "action": "...", "steps": [ { "title": "Inspect current releases", "target": "Workflows ...", "description": "...", "cli_commands": [{ "cmd": "cargo-ai ...", "description": "..." }] } ], "cli_commands": [{ "cmd": "cargo-ai whoami", "description": "Verify auth" }] }, "_hash": "e7108da8a233" }
{
"ok": true,
"from_cache": true,
"plan": { /* same plan object as the done event above */ },
"_hash": "e7108da8a233"
}GET /downstream/risks/{prop_id}/actions first to inspect has_cached_plan before streaming. The plan is not a text to paste: /actions/apply writes it as a draft in your tool.Check action cache status
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/risks/linkedin-api:cargo-api:risk/actions
{
"ok": true,
"data": {
"prop_id": "linkedin-api:cargo-api:risk",
"available_actions": ["fix"],
"has_cached_plan": true,
"cached_at": "2026-10-23T10:00:00.000Z"
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}POST /downstream/capabilities/{prop_id}/actions/build
Same streaming behavior as /actions/fix but for capability propagations: the parry is a new workflow in your orchestrator. A companion GET .../actions endpoint reports has_cached_plan.
curl -sN -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/downstream/capabilities/cargo-api:hubspot-crm-v3:opportunity/actions/build
data: {"type":"token","text":"..."}
...
data: {"type":"done","plan":{...},"_hash":"..."}Writing a parry
A parry lands in two places: the orchestrator (Cargo and n8n flows) and the code (pull requests on GitHub). Nothing is deployed without the user's go.
In the orchestrator, the parry is saved as a draft in your flow and waits for your deploy. In the code, it is a pull request you review and merge. /actions/fix and /actions/build plan the parry, the two routes below write it, and GET /outcomes follows it after that.
POST /downstream/{risks|capabilities}/{prop_id}/actions/apply
Writes a parry into the orchestrator. Send the plan returned by /actions/fix or /actions/build, unchanged. Cargo steps go through the Cargo SDK, n8n steps through the n8n API. By default each flow is saved as a draft: it runs once you deploy it in your tool.
| Field | Type | Description |
|---|---|---|
| plan | object | Required. The plan, with its steps array. |
| planId | string | Optional. Returned as is in the response. |
| approved | bool | Must be true when the plan holds a risky step: a mutating HTTP call, a destructive action, or a connector rate limit. Without it, nothing runs and the response lists the risky steps. |
| deploy | bool | Your go in the same call: true deploys live instead of saving a draft. Default: draft. |
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "plan": { "steps": [ ... ] } }' \ https://parrad.app/api/v1/downstream/risks/linkedin-api:cargo-api:risk/actions/apply
{
"ok": true,
"planId": "plan-7f3a",
"log": [
{ "step": 0, "kind": "workflow.patch", "status": "draft", "workflowUuid": "...", "diff": [ /* what changed */ ], "nodeCount": 12 }
],
"stepResults": [ /* one per step */ ],
"deepLinkUrl": "https://...",
"open_in_cargo": { "workflow_uuid": "...", "url": "https://..." }
}{
"ok": false,
"planId": "plan-7f3a",
"requires_approval": true,
"risky": [ { "stepIndex": 1, "title": "...", "reasons": ["..."] } ]
}log entry says draft or deployed; for n8n the status is read back from your instance. A step that fails stops the plan and returns ok: false with failedStep and error. Returns 400 if plan.steps is missing and 503 if the orchestrator is not connected. Same route under /downstream/capabilities/{prop_id}/actions/apply.POST /github/sync
Writes a parry into the code: opens a pull request in your repo, never a push to your default branch. The merge stays yours. If the pull request is already open, it is updated.
| Field | Type | Description |
|---|---|---|
| repo | string | Required. owner/name. |
| type | string | skill | sdk | cli. Default: skill. |
| spec | object | The content of the file. skill needs guidance, sdk needs package, cli needs binary and commands. |
| propId | string | Optional. Records the pull request on that propagation. |
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "repo": "acme/gtm-agents", "type": "sdk", "spec": { "package": "stripe", "version": "19.1.0" } }' \ https://parrad.app/api/v1/github/sync
{
"ok": true,
"data": {
"ok": true,
"prUrl": "https://github.com/acme/gtm-agents/pull/42",
"prNumber": 42,
"branch": "parrad/sync-stripe",
"base": "main",
"action": "created", // "created" | "updated"
"fileAction": "created",
"preflight": { /* checks run before publishing */ }
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}repo is missing or spec is invalid, 422 if GitHub is not connected, 502 if GitHub refuses the write.GET /outcomes
Every parry written to an orchestrator, and whether it held. apply_status says where it stands: drafted, applied, failed or reverted. verified is computed at read time, and your own verdict wins over it. The full contract is served by GET /schema/outcome.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/outcomes
{
"ok": true,
"data": {
"outcomes": [
{
"id": "600fc38c-...",
"root_api": "linkedin-api",
"kind": "risk",
"backend": "cargo", // "cargo" | "n8n"
"workflow_uuid": "...",
"apply_status": "applied",
"verified": true,
"verified_source": "derived", // "derived" | "user"
"created_at": "2026-10-23T10:00:00.000Z"
}
],
"summary": { "total": 1, "applied": 1, "verified": 1 }
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}Give your verdict on a parry
curl -X PATCH -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "verified": true }' \ https://parrad.app/api/v1/outcomes/600fc38c-...
{ "ok": true, "data": { "id": "600fc38c-...", "verified": true, "judged_by": "user" }, "meta": { /* ... */ } }verified takes true, false or null. Returns 400 for anything else.Revert a parry
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_uuid": "..." }' \ https://parrad.app/api/v1/outcomes/600fc38c-.../revert
{ "ok": true, "data": { "id": "600fc38c-...", "workflow_uuid": "...", "reverted": true, "plan_reverted": true }, "meta": { /* ... */ } }plan_reverted turns true when the last flow is back. On n8n, unpublish: true also takes the workflow offline. Returns 409 if your tool refuses the revert.GET /verdicts
Human verdicts on signals and plans, with a per-signal-type summary. An agent reads them before trusting a plan. Filter with signal_type, verdict, target_kind, api_id, since, until and limit (default 100, max 500).
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ "https://parrad.app/api/v1/verdicts?signal_type=deprecation_announced"
{
"ok": true,
"data": {
"verdicts": [ /* newest verdicts */ ],
"total": 4,
"by_signal_type": [ /* aggregate per signal type */ ],
"auto_tighten_threshold": 3,
"rule_tightenings": {},
"slo": { /* agree_min_pct, override_max_pct */ }
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}Verdict summary for one signal type
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ "https://parrad.app/api/v1/verdicts/summary?signal_type=deprecation_announced"
{ "ok": true, "data": { "summary": { "good": 3, "bad": 1, "unclear": 0, "positive": 0, "negative": 0, "total": 4 } }, "meta": { /* ... */ } }signal_type is required: 400 without it. summary is null when no human has judged that type yet.GET /entity/{signal_key}/timeline
Returns the chain of one vendor change, the same chain the dashboard shows, in nine steps: Captured, Matched, Chosen, Planned, Decided, Drafted, Applied, Held, Revenue. Each step says what it asserts, who plays it (agent is Parrad, user is you), its date and its state.
signal_key has the form {api_id}:{signal_type}:{slug}. It is the top_signal_key of an alert. Returns 404 if nothing is known about that key.
| Field | Description |
|---|---|
| entity | The change itself: vendor, source and detection date. |
| progress | done, total, current, terminal, plus stall and next: the next step, who plays it, and how long the chain has waited. |
| beats | One entry per step, with its dated state: done | active | pending | skipped. A chain can end on broken, reverted or not_needed. |
| parade | The parry card for a change that touches a flow, one line per flow. Otherwise null. |
| integrity | ok and violations: a step the chain went past is declared here, never filled in. |
| steps | The nine steps in order, with id, label, actor and asserts. |
| verdict_gate | Whether a verdict can be given at this point of the chain. |
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/entity/firecrawl-api:new_endpoint:parse/timeline
{
"ok": true,
"data": {
"signal_key": "firecrawl-api:new_endpoint:parse",
"entity": { /* the change */ },
"progress": { "done": 4, "total": 9, "current": "planned", "terminal": null, /* stall, next */ },
"beats": [
{
"id": "captured",
"title": "Captured",
"asserts": "the vendor changed something, with the quote that proves it",
"actor": "agent",
"date": "2026-10-20T08:00:00.000Z",
"state": "done"
}
/* one per step, through revenue */
],
"parade": { /* the parry card */ },
"integrity": { "ok": true, "violations": [] },
"steps": [ /* the nine steps */ ],
"verdict_gate": { /* ... */ }
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /webhooks
Manage outbound webhooks. Parrad sends a signed POST to every enabled webhook subscribed to the event. Max 10 webhooks per workspace. A new webhook subscribes to ["alert.created"].
| Event | When it is sent |
|---|---|
| alert.created | A new alert opens with urgency high or critical, or level high or red. Sent again when a standing alert escalates under the same conditions, unless it is muted. |
| alert.resolved | An open alert closes because its signal is no longer live. Sent only to webhooks with alert.resolved in events_subscribed; POST /webhooks takes no events field. |
| webhook.test | The Test button in Settings → Webhooks. Same shape as alert.created, with sample data. |
Content-Type: application/json User-Agent: Parrad-Webhook/1.0 X-PARRAD-Signature: sha256=<hex>
{
"event": "alert.created",
"timestamp": "2026-10-17T06:01:00.000Z",
"data": {
"id": "m8k2a9xz",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"kind": "risk",
"state": "open",
"situation": "HubSpot v1 Lists API sunset announced for April 2027.",
"action": "Migrate to v3 Lists API before Q1 2027.",
"urgency": "high",
"score": 52,
"previous_level": "yellow",
"new_level": "orange",
"top_signal": "deprecation_announced",
"changelog_url": null,
"docs_url": null
}
}{
"event": "alert.resolved",
"timestamp": "2026-10-20T06:01:00.000Z",
"data": {
"id": "m8k2a9xz",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"kind": "risk",
"state": "resolved",
"resolved_at": "2026-10-20T06:00:58.000Z"
}
}Verify the signature
X-PARRAD-Signature carries sha256= and the HMAC-SHA256 of the raw request body, keyed with your signing secret. The secret is in Settings → Webhooks, where you can also rotate it. Compute the HMAC on the raw bytes, before parsing the JSON.
import { createHmac, timingSafeEqual } from 'node:crypto' function isFromParrad(rawBody, signature, secret) { const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex') return signature?.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) }
enabled: false).List webhooks
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/webhooks
{
"ok": true,
"data": {
"webhooks": [
{
"id": "mh2k9x4qf7a1c",
"url": "https://hooks.example.com/parrad",
"label": "My Slack webhook",
"enabled": true,
"created_at": "2026-10-01T09:00:00.000Z",
"events_subscribed": ["alert.created"]
}
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}Register a webhook
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.example.com/parrad", "label": "My Slack webhook" }' \ https://parrad.app/api/v1/webhooks
{ "ok": true, "data": { "webhook": { /* full webhook object */ } }, "meta": { /* ... */ } }https://. Returns 400 if missing. label is optional; defaults to the URL.Remove a webhook
curl -X DELETE -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/webhooks/mh2k9x4qf7a1c
{ "ok": true, "data": { "deleted": "mh2k9x4qf7a1c" }, "meta": { /* ... */ } }GET /integrations
List and manage external service integrations. The list shows what is connected among Cargo, Anthropic, Firecrawl and GitHub. Only cargo is configurable through this route; the others are set in Settings or on the server.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/integrations
{
"ok": true,
"data": {
"integrations": [
{ "provider": "cargo", "connected": true, "connected_at": "2026-10-01T09:00:00.000Z" },
{ "provider": "anthropic", "connected": true },
{ "provider": "firecrawl", "connected": true },
{ "provider": "github", "connected": true }
]
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}Connect Cargo
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "api_key": "cargo_..." }' \ https://parrad.app/api/v1/integrations/cargo
{ "ok": true, "data": { "provider": "cargo", "connected": true }, "meta": { /* ... */ } }Disconnect Cargo
curl -X DELETE -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/integrations/cargo
{ "ok": true, "data": { "provider": "cargo", "connected": false }, "meta": { /* ... */ } }POST /cache/invalidate
Clears the signal collection cache for a specific API (or all APIs). The next run will collect fresh signals regardless of the normal deduplication window. Useful after a manual source update or when a known change wasn't picked up.
curl -X POST -H "X-PARRAD-Key: $PARRAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "api_id": "hubspot-crm-v3" }' \ https://parrad.app/api/v1/cache/invalidate
-d '{ "api_id": "all" }'
{
"ok": true,
"data": {
"invalidated": "hubspot-crm-v3",
"message": "signal cache cleared for hubspot-crm-v3 — next run will collect fresh"
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /hub
The map an agent reads first, in one call: the objects Parrad serves, how they relate, the flows by intent, and where to start. GET /api/v1 lists the routes.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/hub
{
"ok": true,
"data": {
"version": "1.0",
"base": "https://parrad.app/api/v1",
"start_here": { "message": "...", "call": "..." },
"objects": [
{
"name": "signal",
"one_line": "Something changed on an API you depend on.",
"schema": "/api/v1/schema/signal",
"read_route": "/api/v1/signals",
"produces": ["alert", "plan"]
}
],
"flows": [ /* what to call, by intent */ ],
"human_judgment": { "list_route": "/api/v1/verdicts", /* ... */ },
"docs": { "route_manifest": "/api/v1", "human_reference": "/docs" }
},
"meta": { "api_version": "1.0", "timestamp": "2026-10-23T10:00:00.000Z" }
}GET /schema/{name}
Serves the YAML contract of one object, so an agent can validate what it reads. name is one of signal, alert, plan, outcome, run-legs, endpoint-check, artifact-check, prompt-context. Any other name returns 404.
curl -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/schema/outcome
schema_version: "1.0" outcome: id: string # ...
Cargo
Cargo is one of the places a parry is written. Connect your key with POST /integrations/cargo: Parrad reads your flows, matches each vendor change to the nodes it touches, and writes the parry into the flow as a draft.
Write a parry into a Cargo flow
Plan it, write it, then deploy it in Cargo. The draft stays in your workspace and nothing runs until you deploy it.
POST /api/v1/downstream/risks/{prop_id}/actions/fix // plan the parry
POST /api/v1/downstream/risks/{prop_id}/actions/apply // write it, status "draft"
Open deepLinkUrl, review the draft, deploy it in Cargo
GET /api/v1/outcomes // follow it: drafted, applied, verifiedRead the same signal from a Cargo flow
A flow can also read the feed: poll GET /api/v1/alerts on a schedule and push the payload into a Cargo HTTP source or a Code node.
curl -s -H "X-PARRAD-Key: $PARRAD_API_KEY" \ https://parrad.app/api/v1/alerts \ | jq '.data.alerts[] | select(.read == false)'
{
"id": "m8k2a9xz",
"api_id": "hubspot-crm-v3",
"api_name": "HubSpot CRM API v3",
"previous_level": "yellow",
"new_level": "orange",
"score": 52,
"timestamp": "2026-10-17T06:01:00.000Z",
"top_signal": "deprecation_announced"
}// Cargo code node: for each unread alert, create a HubSpot note const resp = await fetch('https://parrad.app/api/v1/alerts', { headers: { 'X-PARRAD-Key': process.env.PARRAD_API_KEY } }) const { data } = await resp.json() const unread = data.alerts.filter(a => !a.read) return unread.map(alert => ({ subject: `Parrad ${alert.new_level.toUpperCase()}: ${alert.api_name}`, body: `Score moved ${alert.previous_level} → ${alert.new_level} (${alert.score}/100). Signal: ${alert.top_signal}`, api_id: alert.api_id, severity: alert.new_level, }))
n8n
n8n is the other orchestrator where a parry is written. Connect your key and instance URL in Settings → Integrations. POST …/actions/apply writes the parry into your workflow through the n8n API, and the log reports the status read back from your instance. You publish it in n8n when you are ready.
Read the same signal from an n8n workflow
A workflow can also consume the feed. Add an HTTP Request node to trigger a scan of the APIs you name, then poll for results and route on severity.
{
"method": "POST",
"url": "https://parrad.app/api/v1/runs",
"headers": {
"X-PARRAD-Key": "{{ $env.PARRAD_API_KEY }}",
"Content-Type": "application/json"
},
"body": {
"api_ids": ["hubspot-crm-v3"]
}
}{
"method": "GET",
"url": "https://parrad.app/api/v1/alerts",
"headers": { "X-PARRAD-Key": "{{ $env.PARRAD_API_KEY }}" }
}// Condition: alert is unread AND new_level is orange or red
{{ $json.data.alerts
.filter(a => !a.read && ['orange','red'].includes(a.new_level))
.length > 0 }}Schedule Trigger (daily 08:00) → HTTP Request: POST /api/v1/runs // fire scan (api_ids required) → Wait: 120s // wait for run to complete → HTTP Request: GET /api/v1/alerts // fetch results → Code node: filter unread orange + red → IF: any alerts found? YES → your own routing NO → no-op