Copy for AI assistant
Paste this entire document into Claude, ChatGPT, Cursor, or any AI for complete API context.
Outerfaced API — AI Context Document
Copy-paste this entire document into any AI assistant (Claude, ChatGPT, Cursor, etc.) for complete working knowledge of the Outerfaced API.
Section 1: What Outerfaced Is
Outerfaced lets you give your automation workflows a shareable, real-time web interface — without writing any frontend code. You create a channel (a live mini-dashboard with its own URL), then push cards to it via a single POST request from any HTTP client. A card is composed of blocks — the atomic UI units: text, badge, key-value rows, images, buttons, inputs, selects, and more. When a visitor interacts with a block (clicks a button, submits an input, changes a toggle), Outerfaced fires a webhook to your configured URL — that's an interaction, and it's how the loop closes: your automation sees the human's response and continues. The core mental model is: channel = shareable live interface, card = content unit you push via API, blocks = the building blocks of a card, interactions = what fires when a user does something.
Section 2: Base URL and Authentication
Base URL: https://outerfaced.com/api/v1
Authentication: Every v1 API call requires a Bearer token in the Authorization header. API keys have the prefix chnd_sk_. Keys are workspace-scoped — you do not pass a workspace ID in requests; the key identifies the workspace.
curl https://outerfaced.com/api/v1/channels \
-H "Authorization: Bearer chnd_sk_your_key_here"
Generate an API key from the dashboard, or via:
curl -X POST https://outerfaced.com/api/user/api-key \
-H "Cookie: <your-session-cookie>"
# Returns { "key": "chnd_sk_...", "prefix": "chnd_sk_..." }
# Plaintext shown ONCE — store it immediately.
Public routes (no auth required):
POST /api/public/interactions— fires when a visitor clicks/submits something on a channel. This is called by the Outerfaced frontend, not by you. No Bearer token needed. Channel visibility (private/protected/public) is enforced here instead.
Section 3: Every API Route
Channels
List channels
# GET /api/v1/channels
# Returns all channels in the workspace
curl "https://outerfaced.com/api/v1/channels" \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
[
{
"id": "chnl_4d72e1a3b9c0",
"name": "SMS Lead Review",
"description": "Inbound SMS leads requiring human qualification.",
"mode": "interactive",
"icon": "📱",
"workspace_id": "ws_a1b2c3d4",
"created_at": "2024-11-15T09:00:00.000Z",
"card_count": 14
}
]
Create a channel
# POST /api/v1/channels
curl -X POST https://outerfaced.com/api/v1/channels \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "SMS Lead Review",
"description": "Inbound SMS leads requiring human qualification.",
"mode": "interactive",
"icon": "📱",
"webhook_url": "https://your-server.com/webhooks/sms-leads",
"workspace_id": "ws_a1b2c3d4",
"attributes": {
"region": "us-west",
"pipeline": "inbound-sms"
}
}'
# Response (201)
{
"id": "chnl_4d72e1a3b9c0",
"name": "SMS Lead Review",
"url": "https://outerfaced.com/c/chnl_4d72e1a3b9c0",
"workspace_id": "ws_a1b2c3d4"
}
Fields: name (required, max 80 chars), workspace_id (required), description (optional, max 300), mode ("view" | "interactive", default "view"), icon (emoji), webhook_url, attributes (flat key-value object). Limit: 50 channels/day.
Get a channel
# GET /api/v1/channels/:channelId
curl "https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0" \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
{
"id": "chnl_4d72e1a3b9c0",
"name": "SMS Lead Review",
"description": "Inbound SMS leads requiring human qualification.",
"mode": "interactive",
"icon": "📱",
"webhook_url": "https://your-server.com/webhooks/sms-leads",
"workspace_id": "ws_a1b2c3d4",
"visibility": "public",
"created_at": "2024-11-15T09:00:00.000Z",
"actions": [
{ "id": "actn_1e3b5d7f", "label": "Refresh All", "style": "secondary", "position": 0 }
]
}
Update a channel
# PATCH /api/v1/channels/:channelId
curl -X PATCH https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0 \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "SMS Lead Review — Active",
"webhook_url": "https://your-server.com/webhooks/sms-leads-v2",
"mode": "interactive",
"icon": "🔥",
"description": "Only showing hot leads."
}'
# Response: full updated channel object
Delete a channel
# DELETE /api/v1/channels/:channelId
# Cascades: deletes all cards, actions, attributes, interaction logs
curl -X DELETE https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0 \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
{ "deleted": true }
Cards
List cards
# GET /api/v1/channels/:channelId/cards
# Returns cards ordered by created_at DESC
curl "https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards" \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
[
{
"id": "card_7b2f4a1d3e8c",
"channel_id": "chnl_4d72e1a3b9c0",
"status": "pending",
"blocks": [...],
"created_at": "2024-11-15T14:32:00.000Z"
}
]
Push a card
# POST /api/v1/channels/:channelId/cards
curl -X POST https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "pending",
"blocks": [
{
"type": "text",
"id": "blk_01",
"content": "### New inbound SMS lead\nReceived from +1 (555) 867-5309. Review and qualify below.",
"size": "md"
},
{
"type": "badge",
"id": "blk_02",
"label": "Awaiting Review",
"variant": "pending"
},
{
"type": "kv",
"id": "blk_03",
"rows": [
{ "key": "Phone", "value": "+1 (555) 867-5309" },
{ "key": "Message", "value": "Hi, I saw your ad about solar panels. Interested." },
{ "key": "Received", "value": "2024-11-15 14:31 UTC" },
{ "key": "Source", "value": "Google Ads — Solar Campaign" }
]
},
{
"type": "button",
"id": "blk_04",
"buttons": [
{ "id": "btn_qualify", "label": "Qualify", "style": "primary", "disabled": false },
{ "id": "btn_disqualify", "label": "Disqualify", "style": "danger", "disabled": false },
{ "id": "btn_callback", "label": "Schedule Callback", "style": "secondary", "disabled": false }
]
}
]
}'
# Response (201)
{
"id": "card_7b2f4a1d3e8c",
"channel_id": "chnl_4d72e1a3b9c0",
"created_at": "2024-11-15T14:32:00.000Z"
}
Fields: blocks (required, array), status ("default" | "success" | "warning" | "error" | "pending"). Block IDs auto-assigned as UUIDs if omitted — but set them explicitly if you need to patch blocks later. Limit: 200 cards/day. Card changes broadcast via Supabase Realtime to all viewers of the channel instantly.
Update a card
# PATCH /api/v1/channels/:channelId/cards/:cardId
# Patch the card's status and/or specific blocks by their id
curl -X PATCH https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards/card_7b2f4a1d3e8c \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "success",
"blocks": [
{
"id": "blk_02",
"patch": {
"label": "Qualified",
"variant": "success"
}
},
{
"id": "blk_04",
"patch": {
"buttons": [
{ "id": "btn_qualify", "disabled": true },
{ "id": "btn_disqualify", "disabled": true },
{ "id": "btn_callback", "disabled": true }
]
}
},
{
"id": "blk_03",
"patch": {
"append_rows": [
{ "key": "Decision", "value": "Qualified by Sarah" },
{ "key": "Next Step", "value": "AE call booked for Nov 16 at 10am" }
]
}
}
]
}'
# Response
{
"id": "card_7b2f4a1d3e8c",
"channel_id": "chnl_4d72e1a3b9c0",
"status": "success",
"blocks": [...],
"updated_at": "2024-11-15T14:35:00.000Z"
}
Patch semantics:
blocks[].patchis a shallow merge onto the block's current fields.- KV blocks: use
append_rows(array) to add rows without replacing existing ones. Usingrowsdirectly replaces all rows. - Button blocks: pass
buttonsas an array of{ id, ...fields }to patch individual buttons by id. Only buttons whose id matches get updated. - All other blocks: patch merges into top-level fields.
Delete a card
# DELETE /api/v1/channels/:channelId/cards/:cardId
curl -X DELETE https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards/card_7b2f4a1d3e8c \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response: 204 No Content
Attributes
Channel attributes are key-value metadata attached to a channel. They display in the channel UI and can be used as filters in workspace sidebars. Keys are auto-lowercased.
Get all attributes
# GET /api/v1/channels/:channelId/attributes
curl "https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/attributes" \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
{
"region": "us-west",
"pipeline": "inbound-sms",
"assigned_to": "[email protected]",
"created_at": "2024-11-15T09:00:00.000Z",
"updated_at": "2024-11-15T14:35:00.000Z"
}
Upsert attributes
# PUT /api/v1/channels/:channelId/attributes
# Only provided keys are updated — omitted keys are untouched.
# Empty string clears an attribute's value. Reserved keys: created_at, updated_at (forbidden).
curl -X PUT https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/attributes \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"assigned_to": "[email protected]",
"status": "active",
"tier": "enterprise"
}'
# Response: full updated attributes object
Delete an attribute
# DELETE /api/v1/channels/:channelId/attributes/:key
curl -X DELETE "https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/attributes/tier" \
-H "Authorization: Bearer chnd_sk_your_key_here"
# Response
{ "deleted": true }
Interactions
Interactions are fired by the Outerfaced frontend when a visitor interacts with a channel. You do not call this endpoint — Outerfaced calls it internally and then forwards the event to your webhook_url. The endpoint is public (no Bearer token).
# POST /api/public/interactions
# Called by Outerfaced frontend, not by your automation.
# Visibility is enforced: private channels return 403, protected channels require cookie.
curl -X POST https://outerfaced.com/api/public/interactions \
-H "Content-Type: application/json" \
-d '{
"channel_id": "chnl_4d72e1a3b9c0",
"type": "button_click",
"pub_card_token": "<encrypted-token-from-frontend>",
"pub_action_token": null,
"payload": {
"button_id": "btn_qualify",
"label": "Qualify"
}
}'
# Response
{
"id": "intr_9f3a1bc2d4e5",
"channel_id": "chnl_4d72e1a3b9c0",
"type": "button_click",
"payload": { "button_id": "btn_qualify", "label": "Qualify" },
"created_at": "2024-11-15T14:32:07.412Z"
}
Workspaces
These routes use Supabase session auth (dashboard login), not API key Bearer tokens. Workspace IDs are inferred from the API key on v1 routes — you do not need workspace endpoints in your automation scripts.
# GET /api/workspaces — list all workspaces for logged-in user
# POST /api/workspaces — create workspace (body: { "name": "...", "icon": "🏢" })
# GET /api/workspaces/:id — get a workspace
# PATCH /api/workspaces/:id — update (name, icon, visibility, layout, custom_slug, etc.)
# DELETE /api/workspaces/:id — delete (cannot delete last workspace; cascades everything)
API Keys
Session-authenticated (dashboard login). Plaintext returned once on creation only.
# POST /api/user/api-key — generate a new key
curl -X POST https://outerfaced.com/api/user/api-key \
-H "Cookie: <supabase-session-cookie>"
# Response (201): { "key": "chnd_sk_...", "prefix": "chnd_sk_abc123..." }
# DELETE /api/user/api-key — revoke current key
curl -X DELETE https://outerfaced.com/api/user/api-key \
-H "Cookie: <supabase-session-cookie>"
# Response: { "revoked": true }
Section 4: Every Block Type with Full Schema
Every block requires a type field. The id field is optional at creation (auto-assigned UUID) but required when patching. Set explicit IDs on any block you plan to update later.
text
{
"type": "text", // required
"id": "blk_01", // optional at creation, required for patching
"content": "### Lead enriched\nSalesforce match found. Score: **94/100**.", // required — supports markdown
"size": "md", // optional — "sm" | "md" | "lg" | "xl" — default: "md"
"bold": false // optional — applies bold weight to entire block — default: false
}
badge
{
"type": "badge", // required
"id": "blk_02", // optional
"label": "Qualified", // required
"variant": "success" // required — "default" | "success" | "warning" | "error" | "pending"
}
kv
{
"type": "kv", // required
"id": "blk_03", // optional
"rows": [ // required
{ "key": "Name", "value": "Jordan Ellis" },
{ "key": "Company", "value": "Meridian Labs" },
{ "key": "Title", "value": "VP of Engineering" },
{ "key": "ARR Potential", "value": "$85,000" },
{ "key": "Source", "value": "Inbound SMS" }
]
}
When patching: append_rows adds to existing rows; rows replaces all rows.
image
{
"type": "image", // required
"id": "blk_04", // optional
"url": "https://cdn.example.com/leads/jordan-ellis-profile.png", // required — must be publicly accessible
"alt": "Jordan Ellis LinkedIn profile screenshot" // optional — accessibility alt text
}
divider
{
"type": "divider", // required
"id": "blk_05" // optional
}
No other fields. Renders a horizontal rule. Use to separate sections of a card.
button
{
"type": "button", // required
"id": "blk_06", // optional
"buttons": [ // required
{
"id": "btn_qualify", // required — used to identify which button was clicked in webhook
"label": "Qualify", // required
"style": "primary", // required — "primary" | "secondary" | "danger"
"disabled": false // optional — default: false
},
{
"id": "btn_disqualify",
"label": "Disqualify",
"style": "danger",
"disabled": false
},
{
"id": "btn_callback",
"label": "Schedule Callback",
"style": "secondary",
"disabled": false
}
]
}
input
{
"type": "input", // required
"id": "blk_07", // optional
"label": "Notes for handoff", // optional
"placeholder": "Add context for the account exec…", // optional
"input_type": "text", // optional — "text" | "number" | "email" | "url" — default: "text"
"default_value": "", // optional — pre-filled value
"disabled": false, // optional — default: false
"required": false, // optional — default: false
"clear_on_submit": true, // optional — clears field after submit — default: true
"send_on_change": false // optional — fires webhook on every keystroke — default: false
}
select
{
"type": "select", // required
"id": "blk_08", // optional
"label": "Disposition", // optional
"options": [ // required
{ "value": "hot", "label": "Hot — Call Today" },
{ "value": "warm", "label": "Warm — Follow Up This Week" },
{ "value": "cold", "label": "Cold — Add to Nurture" }
],
"selected_value": "hot", // optional — must match a value in options
"placeholder": "Choose disposition…", // optional
"disabled": false, // optional — default: false
"clear_on_submit": false, // optional — default: true (set false to persist selection)
"send_on_change": true // optional — fires webhook when selection changes — default: false
}
multiselect
{
"type": "multiselect", // required
"id": "blk_09", // optional
"label": "Tags", // optional
"options": [ // required
{ "value": "solar", "label": "Solar Interest" },
{ "value": "homeowner", "label": "Homeowner" },
{ "value": "financing", "label": "Needs Financing" },
{ "value": "high-intent", "label": "High Intent" }
],
"selected_values": ["solar", "homeowner"], // optional — array of pre-selected values
"max_selections": 3, // optional — caps how many items can be selected
"disabled": false, // optional — default: false
"clear_on_submit": false, // optional — default: true
"send_on_change": false // optional — default: false
}
toggle
{
"type": "toggle", // required
"id": "blk_10", // optional
"label": "Send SMS confirmation", // required
"enabled": false, // required — initial on/off state
"disabled": false, // optional — default: false
"description": "Automatically sends a confirmation text to the lead when toggled on.", // optional — helper text
"clear_on_submit": false, // optional — default: true
"send_on_change": true // optional — fires webhook when toggled — default: false
}
Section 5: Webhook Payload Shapes
All webhooks are POST requests to your channel's webhook_url with Content-Type: application/json. Timeout: 10 seconds. No retries on failure.
Button block click
Fired when a visitor clicks a button inside a card's button block.
{
"interaction_id": "intr_9f3a1bc2d4e5",
"channel_id": "chnl_4d72e1a3b9c0",
"card_id": "card_7b2f4a1d3e8c",
"action_id": null,
"type": "button_click",
"payload": {
"button_id": "btn_qualify",
"label": "Qualify"
},
"timestamp": "2024-11-15T14:32:07.412Z"
}
card_id— the card containing the clicked button block.action_id— alwaysnullfor button block clicks.payload.button_id— matches theidyou set on the button object inside the block. Use this to branch your automation logic.payload.label— the button's display label at click time.
Channel action button click
Fired when a visitor clicks a persistent action button in the channel header (not attached to any card).
{
"interaction_id": "intr_2c8d5e1a7f04",
"channel_id": "chnl_4d72e1a3b9c0",
"card_id": null,
"action_id": "actn_1e3b5d7f9a2c",
"type": "header_action",
"payload": {
"label": "Refresh All"
},
"timestamp": "2024-11-15T14:35:22.881Z"
}
card_id— alwaysnull. Header actions are channel-level, not card-level.action_id— ID of the channel action that was clicked.payload.label— the action button's label.
Reply submission
Fired when a visitor submits an input block (with send_on_change: false, the default). This is a form submission — the user typed something and clicked submit.
{
"interaction_id": "intr_3d9e2fb1c507",
"channel_id": "chnl_4d72e1a3b9c0",
"card_id": "card_7b2f4a1d3e8c",
"action_id": null,
"type": "reply",
"payload": {
"block_id": "blk_07",
"block_type": "input",
"value": "Customer confirmed they own their home. Good fit for solar lease program."
},
"timestamp": "2024-11-15T14:38:54.001Z"
}
Input/select/multiselect/toggle change (send_on_change)
Fired when a visitor changes a block that has send_on_change: true. Fires on every change, not on explicit submit.
{
"interaction_id": "intr_5a1c3e7d9b2f",
"channel_id": "chnl_4d72e1a3b9c0",
"card_id": "card_7b2f4a1d3e8c",
"action_id": null,
"type": "input_change",
"payload": {
"block_id": "blk_08",
"block_type": "select",
"value": "hot"
},
"timestamp": "2024-11-15T14:40:11.233Z"
}
payload.block_type—"input"|"select"|"multiselect"|"toggle"payload.value— string forinput/select, string array formultiselect, boolean fortoggle
For multiselect change:
{
"payload": {
"block_id": "blk_09",
"block_type": "multiselect",
"value": ["solar", "homeowner", "high-intent"]
}
}
For toggle change:
{
"payload": {
"block_id": "blk_10",
"block_type": "toggle",
"value": true
}
}
Section 6: Complete End-to-End Example
Scenario: SMS lead qualification. An inbound SMS arrives, your n8n/Make workflow runs enrichment, pushes a review card to a channel, and waits for a human to qualify or disqualify the lead before continuing.
Step 1: Create the channel (once, at setup)
curl -X POST https://outerfaced.com/api/v1/channels \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "SMS Lead Review",
"description": "Inbound SMS leads. Qualify or disqualify before handoff to AE.",
"mode": "interactive",
"icon": "📱",
"webhook_url": "https://your-server.com/webhooks/sms-qualification"
}'
# Save the returned channel id: "chnl_4d72e1a3b9c0"
# Share the URL: https://outerfaced.com/c/chnl_4d72e1a3b9c0
Step 2: Push a card when a new SMS lead arrives
Your automation runs enrichment, then pushes a card:
curl -X POST https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "pending",
"blocks": [
{
"type": "text",
"id": "blk_01",
"content": "### New SMS Lead — Jordan Ellis\nEnrichment complete. Score: **91/100**. Review below.",
"size": "md"
},
{
"type": "badge",
"id": "blk_02",
"label": "Awaiting Review",
"variant": "pending"
},
{
"type": "kv",
"id": "blk_03",
"rows": [
{ "key": "Phone", "value": "+1 (555) 867-5309" },
{ "key": "SMS Message", "value": "Saw your ad about solar panels. Own my home in Phoenix. Interested." },
{ "key": "Name", "value": "Jordan Ellis (enriched)" },
{ "key": "Company", "value": "Self-employed" },
{ "key": "Home Value", "value": "$420,000 (Zillow)" },
{ "key": "Roof Age", "value": "~6 years (permit data)" }
]
},
{
"type": "divider",
"id": "blk_div"
},
{
"type": "button",
"id": "blk_04",
"buttons": [
{ "id": "btn_qualify", "label": "Qualify — Send to AE", "style": "primary", "disabled": false },
{ "id": "btn_disqualify", "label": "Disqualify", "style": "danger", "disabled": false },
{ "id": "btn_more_info", "label": "Need More Info", "style": "secondary", "disabled": false }
]
}
]
}'
# Save: "id": "card_7b2f4a1d3e8c"
The reviewer opens https://outerfaced.com/c/chnl_4d72e1a3b9c0, sees the card appear in real time, and clicks Qualify — Send to AE.
Step 3: Your webhook receives the interaction
Outerfaced POSTs to https://your-server.com/webhooks/sms-qualification:
{
"interaction_id": "intr_9f3a1bc2d4e5",
"channel_id": "chnl_4d72e1a3b9c0",
"card_id": "card_7b2f4a1d3e8c",
"action_id": null,
"type": "button_click",
"payload": {
"button_id": "btn_qualify",
"label": "Qualify — Send to AE"
},
"timestamp": "2024-11-15T14:32:07.412Z"
}
Your handler branches on payload.button_id:
"btn_qualify"→ create CRM contact, assign to AE, send confirmation SMS"btn_disqualify"→ mark lead dead, send opt-out SMS"btn_more_info"→ add to drip sequence, flag for follow-up
Step 4: Update the card to reflect the decision
After your automation handles the interaction, patch the card to show what happened:
# Update badge to "success" + disable all buttons + append decision row to KV
curl -X PATCH https://outerfaced.com/api/v1/channels/chnl_4d72e1a3b9c0/cards/card_7b2f4a1d3e8c \
-H "Authorization: Bearer chnd_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "success",
"blocks": [
{
"id": "blk_02",
"patch": {
"label": "Qualified",
"variant": "success"
}
},
{
"id": "blk_03",
"patch": {
"append_rows": [
{ "key": "Decision", "value": "Qualified by Sarah K." },
{ "key": "AE Assigned", "value": "[email protected]" },
{ "key": "CRM Contact", "value": "salesforce.com/c/003xx0000..." },
{ "key": "SMS Sent", "value": "Confirmation sent at 14:32 UTC" }
]
}
},
{
"id": "blk_04",
"patch": {
"buttons": [
{ "id": "btn_qualify", "disabled": true },
{ "id": "btn_disqualify", "disabled": true },
{ "id": "btn_more_info", "disabled": true }
]
}
}
]
}'
The reviewer sees the card update live — badge turns green, buttons go grey, KV table shows the outcome. No reload needed.
Section 7: Common Patterns
Human-in-the-loop approval
# 1. Push card with Approve/Reject buttons
curl -X POST .../cards -d '{ "blocks": [
{ "type": "text", "id": "t1", "content": "**Wire transfer $12,400** to Acme Corp. Approve?" },
{ "type": "kv", "id": "k1", "rows": [{ "key": "Amount", "value": "$12,400" }, { "key": "Recipient", "value": "Acme Corp — Bank of America" }] },
{ "type": "button", "id": "b1", "buttons": [{ "id": "approve", "label": "Approve", "style": "primary" }, { "id": "reject", "label": "Reject", "style": "danger" }] }
]}'
# 2. Webhook fires with button_id "approve" or "reject"
# 3. PATCH card: update badge to result, disable buttons
Live status update
# Push a card with a "Running" badge, then PATCH as stages complete
curl -X PATCH .../cards/card_abc -d '{
"status": "warning",
"blocks": [
{ "id": "status_badge", "patch": { "label": "Step 2 / 5 — Enriching contacts", "variant": "warning" } },
{ "id": "progress_kv", "patch": { "append_rows": [{ "key": "14:31 UTC", "value": "Salesforce sync complete" }] } }
]
}'
# Repeat for each step; final PATCH sets status success, badge "Complete"
Client reporting card
# Push a weekly summary card with KV + image + badge — no interactive blocks needed
curl -X POST .../cards -d '{
"status": "success",
"blocks": [
{ "type": "text", "id": "t1", "content": "## Weekly SEO Report — Week of Nov 11", "size": "lg" },
{ "type": "badge", "id": "b1", "label": "All Targets Met", "variant": "success" },
{ "type": "kv", "id": "k1", "rows": [
{ "key": "Organic Sessions", "value": "12,440 (+18%)" },
{ "key": "New Backlinks", "value": "34" },
{ "key": "Avg Position", "value": "6.2 (was 8.1)" }
]},
{ "type": "image", "id": "i1", "url": "https://cdn.example.com/charts/seo-week-nov11.png", "alt": "Traffic chart" }
]
}'
Multi-step form across cards
# Card 1: collect initial info, send_on_change false, button to submit
# Webhook fires with reply payload → validate → push Card 2 with next question
# Card 2: deeper qualification with select + input
# Final webhook fires → push confirmation card, send result to CRM
# Card 1
curl -X POST .../cards -d '{ "blocks": [
{ "type": "text", "id": "t1", "content": "**Step 1 of 3** — What is your monthly energy bill?" },
{ "type": "input", "id": "inp1", "label": "Monthly bill (USD)", "input_type": "number", "placeholder": "e.g. 220", "clear_on_submit": false },
{ "type": "button", "id": "b1", "buttons": [{ "id": "next", "label": "Next →", "style": "primary" }] }
]}'
# After webhook fires for "next" click, push Card 2 with the next question...
Quick Reference
| Resource | Daily Limit |
|---|---|
| Channels created | 50 / day |
| Cards created | 200 / day |
| Replies | 100 / day |
| Workspaces | 3 per user (total) |
Limits reset at midnight UTC.
Channel URL pattern: https://outerfaced.com/c/<channelId>
Error shape: { "error": "ErrorType", "message": "human-readable detail" }
Common status codes: 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Rate Limited, 500 Internal Server Error