Pin Types & Schemas
Every pin uses pin_type + pin_config. The API validates pin_config against the schema for that type. Invalid payloads return 400 with code PIN_CONFIG_INVALID.
{
"pin_type": "<type-id>",
"pin_config": { /* shape depends on type — see below */ },
"metadata": { "title": "Required title" }
}TypeScript types are exported from @pindownai/client-js (e.g. MarkdownPinConfig, StatCardsPinConfig, PinConfigByType).
The product ships 43 pin_type values. Sections below group them by use case.
Legend: required fields must be present. All other documented fields are optional unless marked required in a nested object.
Content & text
markdown
Rich text content. Server enables Yjs collaboration when content is provided.
| Field | Type | Required | Description |
|---|---|---|---|
content | string | No | Markdown body |
collaboration.enabled | boolean | No | Set automatically by the server when seeding content |
{
"pin_type": "markdown",
"pin_config": {
"content": "# Weekly report\n\n- Revenue up 18%\n- 3 new customers"
},
"metadata": { "title": "Weekly report" }
}text-media
Text block alongside an image or embed. Set mediaType to "image" or "embed", then provide the matching config object.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Main headline |
text | string | No | Body copy |
subtext | string | No | Secondary paragraph |
textPosition | string | No | "left" or "right" — text side relative to media |
mediaType | string | No | "image" or "embed" |
imageConfig.url | string | No* | Image URL (when mediaType is "image") |
imageConfig.alt | string | No | Image alt text |
imageConfig.fileId | string | No | Reference to an uploaded file |
embedConfig.type | string | No* | e.g. "youtube", "loom", "figma" |
embedConfig.url | string | No* | Embed URL (when mediaType is "embed") |
embedConfig.aspectRatio | string | No | e.g. "16:9", "4:3" |
* Provide imageConfig when using an image, or embedConfig when using an embed.
Image example:
{
"pin_type": "text-media",
"pin_config": {
"title": "Product launch",
"text": "Summary of the Q1 launch initiative.",
"subtext": "Shipped to all workspace customers.",
"textPosition": "left",
"mediaType": "image",
"imageConfig": {
"url": "https://example.com/hero-screenshot.png",
"alt": "Dashboard screenshot"
}
},
"metadata": { "title": "Launch hero" }
}Embed example:
{
"pin_type": "text-media",
"pin_config": {
"title": "Product demo",
"text": "Watch the 2-minute walkthrough.",
"textPosition": "right",
"mediaType": "embed",
"embedConfig": {
"type": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"aspectRatio": "16:9"
}
},
"metadata": { "title": "Demo video" }
}intro
Section intro / heading block.
| Field | Type | Required | Description |
|---|---|---|---|
heading | string | Yes | Main heading |
subheading | string | No | Secondary line |
{
"pin_type": "intro",
"pin_config": {
"heading": "Project overview",
"subheading": "Q1 initiative"
},
"metadata": { "title": "Intro" }
}image
Single image display.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | Image URL |
altText | string | No | Alt text |
fit | string | No | "cover", "contain", etc. |
{
"pin_type": "image",
"pin_config": {
"url": "https://example.com/photo.png",
"altText": "Dashboard screenshot",
"fit": "cover"
},
"metadata": { "title": "Screenshot" }
}gallery
Grid of images.
| Field | Type | Required | Description |
|---|---|---|---|
images | array | No | List of image objects |
images[].url | string | No | Image URL |
images[].alt | string | No | Alt text |
images[].fileId | string | No | Reference to an uploaded file |
{
"pin_type": "gallery",
"pin_config": {
"images": [
{ "url": "https://example.com/a.png", "alt": "Slide A" },
{ "url": "https://example.com/b.png", "alt": "Slide B" }
]
},
"metadata": { "title": "Photo gallery" }
}video
Uploaded or hosted MP4 video player.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | Video file URL |
fileId | string | No | Reference to an uploaded file |
fileName | string | No | Display filename |
mimeType | string | No | "video/mp4" (only supported type) |
caption | string | No | Caption below the player |
size | number | No | File size in bytes |
{
"pin_type": "video",
"pin_config": {
"url": "https://example.com/demo.mp4",
"fileName": "demo.mp4",
"mimeType": "video/mp4",
"caption": "Product walkthrough"
},
"metadata": { "title": "Demo video" }
}audio
Audio player (single track or multi-track playlist). Requires url or at least one entry in tracks.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No* | Primary audio URL |
fileId | string | No | Reference to an uploaded file |
fileName | string | No | Display filename |
mimeType | string | No | "audio/mpeg", "audio/wav", or "audio/x-wav" |
caption | string | No | Caption below the player |
size | number | No | File size in bytes |
tracks | array | No* | Up to 10 tracks (playlist mode) |
tracks[].url | string | Yes | Track URL |
tracks[].fileName | string | No | Track label |
* Provide url for a single track, or tracks with at least one item.
{
"pin_type": "audio",
"pin_config": {
"url": "https://example.com/podcast.mp3",
"fileName": "podcast.mp3",
"mimeType": "audio/mpeg",
"caption": "Episode 12"
},
"metadata": { "title": "Podcast clip" }
}business-card
CV-style contact section with key–value fields and optional photo.
| Field | Type | Required | Description |
|---|---|---|---|
sectionTitle | string | No | Section heading |
fields | array | Yes (min 1) | Key–value rows |
fields[].id | string | Yes | Field id |
fields[].key | string | Yes | Label (e.g. "E-Mail") |
fields[].value | string | Yes | Value |
imageConfig.url | string | No | Photo URL |
imageConfig.alt | string | No | Photo alt text |
imageConfig.fileId | string | No | Reference to an uploaded file |
imagePosition | string | No | "left" or "right" |
{
"pin_type": "business-card",
"pin_config": {
"sectionTitle": "Contact",
"fields": [
{ "id": "f1", "key": "Name", "value": "Jane Doe" },
{ "id": "f2", "key": "E-Mail", "value": "jane@example.com" }
],
"imageConfig": { "url": "https://example.com/photo.jpg", "alt": "Jane Doe" },
"imagePosition": "right"
},
"metadata": { "title": "Team contact" }
}notes
Quick-capture note memory (title + optional body per note).
| Field | Type | Required | Description |
|---|---|---|---|
notes | array | No | Note entries |
notes[].id | string | No | Note id |
notes[].title | string | Yes | Note title |
notes[].content | string | No | Note body |
notes[].order | number | No | Sort order |
{
"pin_type": "notes",
"pin_config": {
"notes": [
{ "id": "n1", "title": "Meeting takeaway", "content": "Ship API v1 by Friday.", "order": 0 },
{ "id": "n2", "title": "Follow-up", "content": "Schedule design review.", "order": 1 }
]
},
"metadata": { "title": "Scratchpad" }
}Data & metrics
stat-cards
KPI cards with optional trend indicators.
| Field | Type | Required | Description |
|---|---|---|---|
cards | array | Yes (min 1) | Stat card list |
cards[].title | string | Yes | Metric label |
cards[].value | string | number | Yes | Metric value |
cards[].change | string | No | Change label, e.g. "+18.2%" |
cards[].trend | string | No | "up", "down", "neutral" |
cards[].icon | string | No | Icon identifier |
{
"pin_type": "stat-cards",
"pin_config": {
"cards": [
{ "title": "Revenue", "value": "$125,430", "change": "+18.2%", "trend": "up" },
{ "title": "Users", "value": 1234, "change": "+5%", "trend": "up" }
]
},
"pin_layout": "2x1",
"metadata": { "title": "KPI dashboard" }
}charts
Data visualizations (line, bar, pie, area, radar, etc.).
| Field | Type | Required | Description |
|---|---|---|---|
chartType | string | No | e.g. "line", "bar", "pie", "area", "radar" |
title | string | No | Chart title |
data | array | No | Data points (objects with arbitrary keys) |
xAxis | string | No | Key used for X axis |
yAxis | string | No | Key used for Y axis |
dataKeys | string[] | No | Series keys to plot |
{
"pin_type": "charts",
"pin_config": {
"chartType": "line",
"title": "API calls",
"xAxis": "name",
"yAxis": "value",
"dataKeys": ["value"],
"data": [
{ "name": "Mon", "value": 1200 },
{ "name": "Tue", "value": 1450 },
{ "name": "Wed", "value": 1680 }
]
},
"pin_layout": "2x2",
"metadata": { "title": "API traffic" }
}table
Tabular data with typed columns.
| Field | Type | Required | Description |
|---|---|---|---|
columns | array | Yes (min 1) | Column definitions |
columns[].id | string | Yes | Column key (used in row cells) |
columns[].name | string | Yes | Column header label |
columns[].type | string | No | e.g. "text", "number", "date" |
rows | array | No | Row objects |
rows[].id | string | No | Row identifier |
rows[].cells | object | No | { [columnId]: value } |
{
"pin_type": "table",
"pin_config": {
"columns": [
{ "id": "product", "name": "Product", "type": "text" },
{ "id": "sales", "name": "Sales", "type": "number" }
],
"rows": [
{ "id": "r1", "cells": { "product": "Widget A", "sales": 1250 } },
{ "id": "r2", "cells": { "product": "Widget B", "sales": 890 } }
]
},
"pin_layout": "2x2",
"metadata": { "title": "Sales table" }
}json-viewer
Pretty-printed JSON object.
| Field | Type | Required | Description |
|---|---|---|---|
json | any | No | Parsed JSON value |
jsonString | string | No | JSON as string (alternative to json) |
title | string | No | Viewer title |
{
"pin_type": "json-viewer",
"pin_config": {
"json": { "status": "ok", "count": 42 },
"jsonString": "{\"status\":\"ok\",\"count\":42}",
"title": "API response"
},
"metadata": { "title": "Payload viewer" }
}json-list
JSON array viewer.
| Field | Type | Required | Description |
|---|---|---|---|
json | array | No | Parsed JSON array |
jsonString | string | No | JSON array as string |
{
"pin_type": "json-list",
"pin_config": {
"json": [{ "id": 1, "name": "Alpha" }, { "id": 2, "name": "Beta" }],
"jsonString": "[{\"id\":1,\"name\":\"Alpha\"}]"
},
"metadata": { "title": "Records list" }
}csv-viewer
CSV data rendered as a table.
| Field | Type | Required | Description |
|---|---|---|---|
csvText | string | No | Raw CSV text |
csvData | array | No | Parsed rows as objects |
fileName | string | No | Display name |
{
"pin_type": "csv-viewer",
"pin_config": {
"csvText": "name,value\nalpha,1\nbeta,2",
"csvData": [
{ "name": "alpha", "value": 1 },
{ "name": "beta", "value": 2 }
],
"fileName": "export.csv"
},
"metadata": { "title": "CSV export" }
}qr-code
QR code for a URL with optional label.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | Target URL encoded in the QR code |
label | string | No | Label shown below the code |
{
"pin_type": "qr-code",
"pin_config": {
"url": "https://pindown.ai",
"label": "Visit Pindown"
},
"metadata": { "title": "QR — Website" }
}social-handles
Social profile links with platform icons.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Section title |
showLabels | boolean | No | Show platform labels |
iconSize | number | No | Icon size in pixels |
handles | array | No | Social entries |
handles[].platform | string | Yes | Platform id (e.g. "x", "linkedin") |
handles[].url | string | Yes | Profile URL |
handles[].label | string | No | Display label |
{
"pin_type": "social-handles",
"pin_config": {
"title": "Follow us",
"showLabels": true,
"handles": [
{ "platform": "x", "url": "https://x.com/pindownai", "label": "@pindownai" },
{ "platform": "linkedin", "url": "https://linkedin.com/company/pindown", "label": "Pindown" }
]
},
"metadata": { "title": "Social links" }
}Files & embeds
embed
Embedded video or external content.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | Embed URL |
type | string | No | e.g. "youtube", "vimeo", "loom" |
{
"pin_type": "embed",
"pin_config": {
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"type": "youtube"
},
"metadata": { "title": "Demo video" }
}pdf-viewer
Inline PDF viewer.
| Field | Type | Required | Description |
|---|---|---|---|
pdf.url | string | Yes | PDF file URL |
pdf.fileName | string | No | Display filename |
pdf.title | string | No | Document title |
{
"pin_type": "pdf-viewer",
"pin_config": {
"pdf": {
"url": "https://example.com/report.pdf",
"fileName": "report.pdf",
"title": "Q4 Report"
}
},
"metadata": { "title": "PDF report" }
}excel-viewer
Inline Excel spreadsheet viewer.
| Field | Type | Required | Description |
|---|---|---|---|
excel.url | string | Yes | Spreadsheet URL |
excel.fileName | string | No | Display filename |
excel.title | string | No | Document title |
{
"pin_type": "excel-viewer",
"pin_config": {
"excel": {
"url": "https://example.com/data.xlsx",
"fileName": "data.xlsx",
"title": "Sales data"
}
},
"metadata": { "title": "Spreadsheet" }
}file-upload
List of uploaded files.
| Field | Type | Required | Description |
|---|---|---|---|
files | array | No | File metadata objects |
displayMode | string | No | e.g. "list", "grid" |
{
"pin_type": "file-upload",
"pin_config": {
"files": [],
"displayMode": "list"
},
"metadata": { "title": "Attachments" }
}Diagrams & planning
mermaid
Mermaid diagram (flowchart, sequence, gantt, etc.). Requires code or diagram.
| Field | Type | Required | Description |
|---|---|---|---|
code | string | YesÂą | Mermaid source |
diagram | string | YesÂą | Alternative to code |
title | string | No | Diagram title |
Âą At least one of code or diagram must be non-empty.
{
"pin_type": "mermaid",
"pin_config": {
"code": "graph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Done]\n B -->|No| D[Retry]",
"title": "Flow"
},
"metadata": { "title": "Architecture diagram" }
}trace
Agent or workflow execution trace with timed spans.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Trace title |
live | boolean | No | Live-updating trace |
spans | array | Yes (min 1) | Execution spans |
spans[].name | string | Yes | Span name |
spans[].status | string | Yes | "completed", "running", "pending", or "failed" |
spans[].type | string | No | "system", "agent", or "tool" |
spans[].timestamp | string | No | Timestamp label |
spans[].duration | string | No | Duration label |
spans[].details | string | No | Extra detail text |
{
"pin_type": "trace",
"pin_config": {
"title": "Workflow run",
"live": false,
"spans": [
{
"id": "1",
"type": "system",
"name": "Initialization",
"status": "completed",
"timestamp": "10:42:01.005",
"duration": "12ms",
"details": "Initialized context"
},
{
"id": "2",
"type": "tool",
"name": "workspace_query",
"status": "completed",
"timestamp": "10:42:01.468",
"duration": "1.2s",
"details": "Searching workspace"
}
]
},
"metadata": { "title": "Agent trace" }
}timeline
Chronological timeline.
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | No | e.g. "alternating", "left", "right" |
items | array | No | Timeline entries |
items[].title | string | No | Date or label |
items[].cardTitle | string | No | Event title |
items[].cardDetailedText | string | No | Event description |
{
"pin_type": "timeline",
"pin_config": {
"mode": "alternating",
"items": [
{
"title": "2024-01-01",
"cardTitle": "Launch",
"cardDetailedText": "Shipped v1 to production"
},
{
"title": "2024-06-01",
"cardTitle": "Series A",
"cardDetailedText": "Closed funding round"
}
]
},
"metadata": { "title": "Company timeline" }
}steps
Numbered step-by-step process.
| Field | Type | Required | Description |
|---|---|---|---|
steps | array | Yes (min 1) | Step list |
steps[].title | string | Yes | Step title |
steps[].description | string | No | Step details |
{
"pin_type": "steps",
"pin_config": {
"steps": [
{ "title": "Install SDK", "description": "npm install @pindownai/client-js" },
{ "title": "Create API key", "description": "Workspace plan → Settings → API" },
{ "title": "Push first pin", "description": "POST /v1/pins" }
]
},
"metadata": { "title": "Getting started" }
}plan
Phased implementation plan with tasks and rich content blocks.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Plan title |
status | string | No | "draft", "in-progress", or "completed" |
breadcrumbs | array | No | Context breadcrumbs ({ label }) |
phases | array | Yes (min 1) | Plan phases |
phases[].title | string | Yes | Phase title |
phases[].status | string | No | Phase status |
phases[].tasks | array | Yes (min 1) | Tasks in the phase |
phases[].tasks[].title | string | Yes | Task title |
phases[].tasks[].blocks | array | No | Content blocks (details, command, code, text, link, mermaid, summary) |
{
"pin_type": "plan",
"pin_config": {
"title": "Implementation Plan",
"status": "in-progress",
"phases": [
{
"id": "p1",
"title": "Phase 1: Setup",
"status": "in-progress",
"tasks": [
{
"id": "t1",
"number": "1.1",
"title": "Install dependencies",
"blocks": [{ "type": "command", "command": "npm install @pindownai/client-js" }]
}
]
}
]
},
"metadata": { "title": "Rollout plan" }
}roadmap
Monthly roadmap grid.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Roadmap title |
months | array | No | Month columns |
months[].month | string | No | Month label |
months[].items | array | No | Items in that month |
months[].items[].id | string | No | Item id |
months[].items[].text | string | No | Item label |
{
"pin_type": "roadmap",
"pin_config": {
"title": "Product roadmap",
"months": [
{ "month": "May", "items": [{ "id": "m1", "text": "API v1" }] },
{ "month": "Jun", "items": [{ "id": "m2", "text": "Mobile app" }] }
]
},
"metadata": { "title": "Roadmap" }
}calendar
Calendar with events.
| Field | Type | Required | Description |
|---|---|---|---|
weekStartsOn | number | No | 0 = Sunday, 1 = Monday |
events | array | No | Calendar events |
events[].id | string | No | Event id |
events[].title | string | No | Event title |
events[].start | string | No | Start date (YYYY-MM-DD) |
events[].end | string | No | End date |
{
"pin_type": "calendar",
"pin_config": {
"weekStartsOn": 0,
"events": [
{ "id": "e1", "title": "Sprint review", "start": "2026-05-01", "end": "2026-05-01" },
{ "id": "e2", "title": "Release", "start": "2026-05-15", "end": "2026-05-15" }
]
},
"metadata": { "title": "Team calendar" }
}kanban-board
Kanban board with columns and cards.
| Field | Type | Required | Description |
|---|---|---|---|
columns | array | Yes (min 1) | Board columns |
columns[].id | string | Yes | Column id |
columns[].title | string | Yes | Column title |
columns[].cards | array | No | Cards in column |
{
"pin_type": "kanban-board",
"pin_config": {
"columns": [
{ "id": "todo", "title": "To do", "cards": [{ "id": "c1", "title": "Design API" }] },
{ "id": "done", "title": "Done", "cards": [] }
]
},
"metadata": { "title": "Sprint board" }
}checklist
Checklist with toggleable items.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Checklist title |
items | array | Yes (min 1) | Checklist rows |
items[].id | string | Yes | Item id |
items[].name | string | Yes | Item label |
items[].checked | boolean | No | Checked state |
{
"pin_type": "checklist",
"pin_config": {
"title": "Launch checklist",
"items": [
{ "id": "c1", "name": "QA pass", "checked": false },
{ "id": "c2", "name": "Deploy prod", "checked": false }
]
},
"metadata": { "title": "Launch checklist" }
}comparison
Side-by-side comparison with column headings, one item per line, optional highlights and row matches.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Pin title |
left | object | Yes | Left column |
left.label | string | Yes | Left heading |
left.body | string | Yes | Multiline text (one row per line) |
right | object | Yes | Right column |
right.label | string | Yes | Right heading |
right.body | string | Yes | Multiline text (one row per line) |
highlightedLines.left | number[] | No | Zero-based line indices to highlight (left) |
highlightedLines.right | number[] | No | Zero-based line indices to highlight (right) |
matches | array | No | Aligned row pairs marked as matching |
matches[].left | number | Yes | Left row index |
matches[].right | number | Yes | Right row index |
showVsBadge | boolean | No | Show VS badge between columns (default true) |
{
"pin_type": "comparison",
"pin_config": {
"title": "Tech stack",
"left": {
"label": "Our stack",
"body": "Next.js\nTypeScript\nFirebase"
},
"right": {
"label": "Their stack",
"body": "React\nJavaScript\nMongoDB"
},
"highlightedLines": { "left": [0], "right": [0] },
"matches": [{ "left": 0, "right": 0 }, { "left": 1, "right": 1 }],
"showVsBadge": true
},
"metadata": { "title": "Stack comparison" }
}links
Collection of link cards.
| Field | Type | Required | Description |
|---|---|---|---|
links | array | No | Link entries |
links[].title | string | No | Link title |
links[].url | string | No | Link URL |
links[].description | string | No | Short description |
{
"pin_type": "links",
"pin_config": {
"links": [
{ "title": "Documentation", "url": "https://pindown.ai" },
{ "title": "GitHub", "url": "https://github.com/pindownai" }
]
},
"metadata": { "title": "Useful links" }
}process-flow
Swimlane workflow map with nodes, edges, team roster, and bottleneck views.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Flow title |
defaultView | string | No | "onboarding" or "bottlenecks" |
swimlanes | array | Yes (min 1) | Lane definitions |
swimlanes[].id | string | Yes | Lane id |
swimlanes[].title | string | Yes | Lane label |
nodes | array | Yes (min 1) | Steps in the flow |
nodes[].id | string | Yes | Node id |
nodes[].swimlane | string | Yes | Lane id this node belongs to |
nodes[].label | string | Yes | Step label |
nodes[].status | string | No | "smooth", "bottleneck", or "warning" |
edges | array | No | { source, target } connections between node ids |
{
"pin_type": "process-flow",
"pin_config": {
"title": "Developer Onboarding Flow",
"defaultView": "onboarding",
"swimlanes": [
{ "id": "po", "title": "Product Owner" },
{ "id": "dev", "title": "Developer" }
],
"nodes": [
{ "id": "1", "swimlane": "po", "label": "Ticket Refinement", "status": "smooth" },
{ "id": "2", "swimlane": "dev", "label": "Implementation", "status": "smooth" }
],
"edges": [{ "source": "1", "target": "2" }]
},
"metadata": { "title": "Onboarding flow" }
}funnel
Conversion funnel with ordered stages, drop-off rates, and optional biggest-leak highlight.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Funnel title |
subtitle | string | No | e.g. time range |
valueFormat | string | No | "number", "percent", or "currency" |
currency | string | No | ISO currency when valueFormat is "currency" |
highlightBiggestDrop | boolean | No | Highlight the largest stage drop-off |
stages | array | Yes (min 1) | Top-to-bottom stages |
stages[].id | string | Yes | Stage id |
stages[].label | string | Yes | Stage label |
stages[].value | number | Yes | Absolute count at this stage |
{
"pin_type": "funnel",
"pin_config": {
"title": "Signup funnel",
"subtitle": "Last 30 days",
"valueFormat": "number",
"highlightBiggestDrop": true,
"stages": [
{ "id": "visit", "label": "Website Visits", "value": 12400 },
{ "id": "signup", "label": "Sign Up Started", "value": 4820 },
{ "id": "paid", "label": "Upgraded to Paid", "value": 640 }
]
},
"metadata": { "title": "Conversion funnel" }
}decision-matrix
Weighted criteria matrix to score options and surface a winner.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Matrix title |
subtitle | string | No | Subtitle |
maxScore | number | No | Max score per criterion (default 5) |
options | array | Yes (min 2) | Columns to compare |
options[].id | string | Yes | Option id |
options[].label | string | Yes | Option label |
criteria | array | Yes (min 1) | Scoring rows |
criteria[].id | string | Yes | Criterion id |
criteria[].label | string | Yes | Criterion label |
criteria[].weight | number | No | Weight multiplier (default 1) |
criteria[].scores | object | Yes | { [optionId]: number } scores |
{
"pin_type": "decision-matrix",
"pin_config": {
"title": "Backend Framework Decision",
"maxScore": 5,
"options": [
{ "id": "next", "label": "Next.js" },
{ "id": "remix", "label": "Remix" }
],
"criteria": [
{
"id": "dx",
"label": "Developer experience",
"weight": 2,
"scores": { "next": 5, "remix": 4 }
},
{
"id": "perf",
"label": "Performance",
"weight": 1,
"scores": { "next": 4, "remix": 5 }
}
]
},
"metadata": { "title": "Framework decision" }
}okr
Objectives and key results with progress, confidence, and status.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | OKR set title |
subtitle | string | No | Subtitle |
period | string | No | e.g. "Q2 2026" |
objectives | array | Yes (min 1) | Objectives list |
objectives[].id | string | Yes | Objective id |
objectives[].title | string | Yes | Objective title |
objectives[].keyResults | array | Yes (min 1) | Key results |
objectives[].keyResults[].id | string | Yes | KR id |
objectives[].keyResults[].title | string | Yes | KR title |
objectives[].keyResults[].current | number | No | Current value |
objectives[].keyResults[].target | number | No | Target value |
objectives[].keyResults[].unit | string | No | Unit label, e.g. "%" |
objectives[].keyResults[].confidence | number | No | 0–100 confidence |
objectives[].keyResults[].status | string | No | "on-track", "at-risk", or "off-track" |
{
"pin_type": "okr",
"pin_config": {
"title": "Q2 Product OKRs",
"period": "Apr – Jun 2026",
"objectives": [
{
"id": "obj-1",
"title": "Accelerate paid conversion",
"keyResults": [
{
"id": "kr-1",
"title": "Trial-to-paid rate",
"current": 18,
"target": 25,
"unit": "%",
"confidence": 65,
"status": "at-risk"
}
]
}
]
},
"metadata": { "title": "Q2 OKRs" }
}Chat, agents & interactive
user-story
User story with acceptance criteria.
| Field | Type | Required | Description |
|---|---|---|---|
userStory | string | Yes | User story text |
title | string | No | Story title |
acceptanceCriteria | string | No | Given/When/Then criteria |
{
"pin_type": "user-story",
"pin_config": {
"title": "Login",
"userStory": "As a user,\nI want to log in\nso that I can access my workspace.",
"acceptanceCriteria": "Given valid credentials\nWhen I sign in\nThen I reach the home page"
},
"metadata": { "title": "US-101 Login" }
}chat
Chat / discussion placeholder.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Chat title |
placeholder | string | No | Input placeholder text |
{
"pin_type": "chat",
"pin_config": {
"title": "Team chat",
"placeholder": "Write a message…"
},
"metadata": { "title": "Discussion" }
}mastra
Mastra workflow or agent pin. Schema is open — pass workflow/agent identifiers as needed.
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | No | "workflow" or "agent" |
workflowId | string | No | Mastra workflow id |
agentId | string | No | Mastra agent id |
{
"pin_type": "mastra",
"pin_config": {
"mode": "workflow",
"workflowId": "my-workflow-id"
},
"metadata": { "title": "Automation runner" }
}realtime-canvas
Collaborative whiteboard / canvas room.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Canvas title |
description | string | No | Short description |
roomId | string | No | Realtime room identifier |
{
"pin_type": "realtime-canvas",
"pin_config": {
"title": "Brainstorm",
"description": "Sprint planning whiteboard",
"roomId": "room-sprint-42"
},
"metadata": { "title": "Whiteboard" }
}Interactive feedback
Pins that collect input in realtime (votes, form answers, star scores). Data lives in separate Firebase paths — not in the pin document.
poll
Live single-question poll. Votes are stored per viewer in Firebase (pin_poll_votes/{pinId}/votes/{userId}), not in the pin document.
| Field | Type | Required | Description |
|---|---|---|---|
question | string | Yes | Poll question |
options | array | Yes (min 2) | Answer choices |
options[].id | string | Yes | Option id |
options[].label | string | Yes | Option label |
status | string | No | "open" or "closed" |
showVoteCount | boolean | No | Show counts and percentages after voting |
allowChangeVote | boolean | No | Let voters change their selection |
{
"pin_type": "poll",
"pin_config": {
"question": "Which feature should we ship next?",
"status": "open",
"showVoteCount": true,
"options": [
{ "id": "a", "label": "Mobile app" },
{ "id": "b", "label": "API v2" }
]
},
"metadata": { "title": "Feature poll" }
}survey
Multi-question form. Responses are stored per respondent in Firebase (pin_survey_responses/{pinId}/responses/{userId}). Owners and editors can read aggregated results in the pin UI.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Survey title |
description | string | No | Intro shown above the form |
status | string | No | "open" or "closed" |
allowEditResponse | boolean | No | Let respondents update answers after submit |
questions | array | Yes (min 1) | Question list |
questions[].id | string | Yes | Question id |
questions[].label | string | Yes | Question text |
questions[].type | string | Yes | "short-text", "long-text", "single-choice", "multiple-choice", or "scale" |
questions[].required | boolean | No | Whether an answer is required |
questions[].options | array | No | Choices for single/multi choice ({ id, label }) |
questions[].scaleMin | number | No | Scale minimum (default 1) |
questions[].scaleMax | number | No | Scale maximum (default 5) |
{
"pin_type": "survey",
"pin_config": {
"title": "Sprint Retro",
"description": "Quick feedback on the last sprint.",
"status": "open",
"allowEditResponse": false,
"questions": [
{ "id": "q1", "label": "What went well?", "type": "long-text", "required": true },
{
"id": "q2",
"label": "Team velocity",
"type": "scale",
"required": true,
"scaleMin": 1,
"scaleMax": 5
},
{
"id": "q3",
"label": "Biggest blocker",
"type": "single-choice",
"options": [
{ "id": "scope", "label": "Scope creep" },
{ "id": "none", "label": "No major blockers" }
]
}
]
},
"metadata": { "title": "Sprint retro" }
}rating
Star rating (default 1–5, half stars optional). Rate one prompt or multiple items (e.g. content, delivery, overall). Scores are stored per viewer in Firebase (pin_ratings/{pinId}/ratings/{userId}). Owners and editors see per-item distribution and averages in the Results tab; optional showAverage displays live per-item averages to viewers.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No | Pin title |
description | string | No | Intro shown above all rating rows |
prompt | string | No | Single rating question (used when items is empty) |
items | array | No | Multiple things to rate — each with id, label, optional description, required |
status | string | No | "open" or "closed" |
maxStars | number | No | 3, 4, or 5 (default 5) |
allowHalfStars | boolean | No | Allow 0.5 increments (default true) |
allowChangeRating | boolean | No | Let users update their scores after submitting |
showAverage | boolean | No | Show per-item live averages to viewers (default true) |
Stored rating at pin_ratings/{pinId}/ratings/{userId}: either a single value (0.5 … maxStars) or a scores object mapping itemId → star value. Half-star steps apply when allowHalfStars is enabled.
Example stored response (multi-item):
{
"scores": {
"demo": 4.5,
"clarity": 5,
"overall": 4
},
"display_name": "Alex",
"rated_at": 1718208000000
}Create example:
{
"pin_type": "rating",
"pin_config": {
"title": "Session feedback",
"description": "Rate each part of today's walkthrough.",
"status": "open",
"maxStars": 5,
"allowHalfStars": true,
"allowChangeRating": true,
"showAverage": true,
"items": [
{ "id": "demo", "label": "Demo usefulness", "required": true },
{ "id": "clarity", "label": "Clarity of explanation", "required": true },
{ "id": "overall", "label": "Overall experience", "required": true }
]
},
"metadata": { "title": "Demo rating" }
}Validation errors
| Code | When |
|---|---|
INVALID_PIN_TYPE | Unknown pin_type value |
PIN_CONFIG_INVALID | pin_config fails schema for the given type |
metadata.title is required | Missing title on create |
Next steps
- Create a Pin — full create endpoint reference
- Update a Pin — partial updates to
pin_config - Client SDK — typed
PinConfigByTypehelpers