Poplar/API Referencev0.1 · beta
Generate a key
Beta · stable contract for 6 weeks

Poplar API

Programmatic access to Poplar. Provision client workspaces, push knowledge into the AI's context, and pull cross-workspace post performance into your own dashboards. Same routes the Poplar web app uses — Bearer auth layers public access on top.

01Section

Introduction

This API is designed for agencies and power users who manage many Poplar workspaces (one per client) or need to integrate Poplar into an existing automation pipeline — pulling transcripts in from your own notetaker, pushing analytics out into your own dashboard.

Available on the Pro and Agency plans, and during the 7-day free trial. Generate keys at Settings → API Keys.

02Section

Quickstart

Get from zero to your first successful API call in under a minute.

  1. 1Log in and go to Settings → API Keys.
  2. 2Click Generate key. Pick a name, scopes (workspaces:read is enough for this quickstart), workspace access, and an expiration. Save the key — it's shown only once.
  3. 3List your workspaces:
bash
curl https://usepoplar.com/api/workspaces \
  -H "Authorization: Bearer pop_live_4f2a8c_..."

You should get back JSON like:

json
{
  "workspaces": [
    { "id": "9d8a3c…", "name": "Acme Corp", "role": "owner", ... },
    { "id": "f12bc8…", "name": "Globex Inc", "role": "owner", ... }
  ]
}
03Section

Authentication

Every request authenticates via the standard Authorization header:

http
Authorization: Bearer pop_live_<prefix>_<secret>

The key string is pop_live_ + a 6-char hex prefix + _ + a 32-byte base64url secret. Treat it like a password. Anyone with the full key string can act as you, scoped to the permissions you granted it.

If a request includes both a Bearer header and a session cookie, Bearer wins.

04Section

Workspace scoping

An API key is tied to a single Poplar user. The key inherits that user's workspace memberships — owner, admin, or member in each workspace.

Optionally, each key has an allow-list:

  • Null (default) — the key can access every workspace you're a member of, now and in the future. New workspaces created via the API are automatically included.
  • Explicit list — only the workspaces you tick. Edit the allow-list anytime (no regeneration needed).

For workspace-scoped endpoints, pass X-Workspace-Id: <uuid>. The server then verifies both: the workspace is in the key's allow-list (or it's null) AND the key's user has the required role in that workspace.

05Section

Endpoints

List workspaces

GET/api/workspaces
scope: workspaces:read

Returns active workspaces this key can access, filtered by the key's allow-list when set.

bash
curl https://usepoplar.com/api/workspaces \
  -H "Authorization: Bearer pop_live_..."

Create a workspace

POST/api/workspaces
scope: workspaces:create

Creates a workspace; you become its owner. Use the returned id as X-Workspace-Id for subsequent calls.

bash
curl -X POST https://usepoplar.com/api/workspaces \
  -H "Authorization: Bearer pop_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "description": "Acme social workspace",
    "timezone": "America/New_York"
  }'

Response:

json
{
  "workspace": {
    "id": "9d8a3c...",
    "name": "Acme Corp",
    "timezone": "America/New_York",
    "settings": { ... }
  }
}

Errors include 403 WORKSPACE_LIMIT_REACHED if your plan caps how many you can own — the response body includes currentCount and maxAllowed.

Create many workspaces (bulk)

POST/api/v1/workspaces/bulk
scope: workspaces:create

Create up to 50 workspaces in a single API call. Processes sequentially so plan-limit checks stay accurate. Returns per-item results so partial failures don't fail the whole request.

bash
curl -X POST https://usepoplar.com/api/v1/workspaces/bulk \
  -H "Authorization: Bearer pop_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "workspaces": [
      { "name": "Acme Corp", "timezone": "America/New_York" },
      { "name": "Globex Inc", "logo_url": "https://example.com/globex.png" },
      { "name": "Initech LLC", "description": "Software consulting client" }
    ]
  }'

Response:

json
{
  "requested": 3,
  "created": 3,
  "failed": 0,
  "results": [
    { "index": 0, "requested_name": "Acme Corp", "ok": true, "workspace": { "id": "...", ... } },
    { "index": 1, "requested_name": "Globex Inc", "ok": true, "workspace": { "id": "...", ... } },
    { "index": 2, "requested_name": "Initech LLC", "ok": true, "workspace": { "id": "...", ... } }
  ]
}

If a workspace fails (e.g., plan limit hit on item 25), the response includes that error in results[25].error AND every subsequent item gets error.code = SKIPPED_AFTER_PLAN_LIMIT — we stop trying so we don't hammer the DB with guaranteed-to-fail attempts. Mapresults back to your input order via results[i].index.

Update a workspace

POST/api/workspaces/settings
scope: workspaces:updateheader: X-Workspace-Id

Owner or admin only. Editable fields: name, logo_url, timezone, settings (free-form JSON).

bash
curl -X POST https://usepoplar.com/api/workspaces/settings \
  -H "Authorization: Bearer pop_live_..." \
  -H "X-Workspace-Id: 9d8a3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp (renamed)",
    "timezone": "America/Los_Angeles"
  }'

Invite a member

POST/api/invitations/send
scope: members:inviteheader: X-Workspace-Id

Sends a one-click accept email via Resend — same template as the UI. Owner / admin only.

bash
curl -X POST https://usepoplar.com/api/invitations/send \
  -H "Authorization: Bearer pop_live_..." \
  -H "X-Workspace-Id: 9d8a3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "amy@acme.com",
    "role": "admin",
    "agentAccess": true,
    "send_email": false
  }'

Body fields: email (required), role (admin | member, default member), agentAccess (whether the invitee can use the AI agent — defaults to role-derived: admin → true, member → false), send_email (default true; set false to suppress the Resend email when the invitee already knows). When suppressed, the response includes accept_url so you can deliver it however you want.

Push a knowledge item

POST/api/knowledge/items
scope: knowledge:writeheader: X-Workspace-Id

Push a transcript, document, link, or page into a workspace's knowledge base — the same store the AI agent reads when drafting posts.

bash
curl -X POST https://usepoplar.com/api/knowledge/items \
  -H "Authorization: Bearer pop_live_..." \
  -H "X-Workspace-Id: 9d8a3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "document",
    "title": "Acme onboarding call",
    "text_content": "Full transcript text...",
    "metadata": {
      "source": "otter",
      "external_id": "otter_abc123"
    }
  }'

Allowed type values: document, link, website, fireflies_transcript, notion_page, notion_database, granola_meeting.

Idempotency: pass a stable metadata.external_id (e.g. your source system's record id). Re-pushing the same (workspace_id, type, external_id) updates the existing row instead of creating a duplicate. The response will show created: false.

Push knowledge from a URL

POST/api/v1/knowledge-items/from-url
scope: knowledge:writeheader: X-Workspace-Id

One-shot: send a URL, Poplar crawls it (Exa — handles LinkedIn posts, JS-rendered pages, paywalled articles), extracts the text, and inserts the knowledge_item with content already populated. No client-side scraping needed.

bash
curl -X POST https://usepoplar.com/api/v1/knowledge-items/from-url \
  -H "Authorization: Bearer pop_live_..." \
  -H "X-Workspace-Id: 9d8a3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://blog.acme.com/founder-led-marketing",
    "external_id": "blog-acme-founder-led-marketing"
  }'

Crawl times out at 20 seconds. Returns the same shape as POST /api/knowledge/items. Idempotent on external_id.

Upload a file (PDF / image)

POST/api/v1/knowledge-items/upload-url
scope: knowledge:writeheader: X-Workspace-Id

Uploading a file is a 2-step flow. Parsing happens automatically — once the upload lands, Poplar extracts the text in the background (typically within ~5–60 seconds) so the AI agent can use it immediately.

  1. Ask for an upload location — POST filename and content_type to /upload-url. You get back a one-time upload URL plus an item_id.
  2. Upload the file — POST the binary to that URL as multipart/form-data, including the fields returned in step 1 and the file under a file field.

Optional Step 3 — synchronous parse. If you need the parsed text back immediately (e.g., for a workflow that posts right after upload), POST to /api/v1/knowledge-items/{id}/parse. It waits for extraction and returns the text. If you skip it, auto-parse handles it within ~60 seconds anyway.

Step 1:

bash
curl -X POST https://usepoplar.com/api/v1/knowledge-items/upload-url \
  -H "Authorization: Bearer pop_live_..." \
  -H "X-Workspace-Id: 9d8a3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "acme-call.pdf",
    "content_type": "application/pdf",
    "title": "Acme onboarding call",
    "external_id": "otter-call-abc123"
  }'

# → returns { item_id, upload: { url, fields, ... }, parsing: { mode: "automatic", ... } }

Step 2 (upload the file):

bash
curl -X POST "<upload.url>" \
  -F "Content-Type=application/pdf" \
  -F "Key=<from upload.fields>" \
  -F "Policy=<from upload.fields>" \
  -F "X-Amz-Algorithm=<from upload.fields>" \
  -F "X-Amz-Credential=<from upload.fields>" \
  -F "X-Amz-Date=<from upload.fields>" \
  -F "X-Amz-Signature=<from upload.fields>" \
  -F "file=@./acme-call.pdf"

# → 204 No Content on success. Parsing kicks off automatically.

Step 3 (optional — wait for parse synchronously):

bash
curl -X POST "https://usepoplar.com/api/v1/knowledge-items/<item_id>/parse" \
  -H "Authorization: Bearer pop_live_..."

# → returns { success, text_length, parsed: { pdf_pages, ... }, ... }

Supported file types: application/pdf (up to 20 MB) and image/* (up to 5 MB). PDFs are parsed for selectable text; images are OCR'd and described so the AI agent has context.

Idempotency on parse: calling /parse twice on the same item returns the cached result instantly. Don't worry about retries.

Read post performance

GET/api/v1/analytics/posts
scope: analytics:read

Published-post performance across the workspaces this key can see. Returns per-post likes, comments, shares, impressions, plus AI-classified post DNA (hook type, structure, topic) when available.

bash
curl "https://usepoplar.com/api/v1/analytics/posts?since=2026-06-01&limit=50" \
  -H "Authorization: Bearer pop_live_..."

Query parameters (all optional):

ParamDefaultNotes
workspace_idRestrict to one workspace.
connection_idRestrict to one social account.
since / untilISO date bounds on published_at.
limit251 – 100.
cursorOpaque, from previous next_cursor.
06Section

Scopes reference

Pick the minimum set of scopes your script actually needs. Scopes are immutable on a key — generate a new key if you need to change them.

ScopeGrants
workspaces:readList + read workspaces and settings
workspaces:createCreate new workspaces
workspaces:updateUpdate workspace settings
members:inviteSend invitation emails
knowledge:writePush items to a workspace knowledge base
analytics:readRead cross-workspace post performance
07Section

Errors

All error responses share this shape (extra fields may be present depending on the error):

json
{ "error": "human-readable message", "code": "MACHINE_CODE" }
CodeHTTPMeaning
VALIDATION400Bad input.
MISSING_WORKSPACE_HEADER400Endpoint requires X-Workspace-Id.
(401)401Missing, invalid, revoked, or expired key.
MISSING_SCOPE403Key doesn't have the required scope.
WORKSPACE_NOT_IN_ALLOWLIST403Target workspace isn't in the key's allow-list.
NOT_MEMBER403Key's user isn't a member of the target workspace.
INSUFFICIENT_ROLE403Member, but role isn't high enough.
PLAN_REQUIRED403User downgraded; Pro or Agency required.
WORKSPACE_LIMIT_REACHED403Plan's workspace cap hit.
RATE_LIMITED429Slow down. Body includes retry_after_seconds.
DB / INTERNAL500Server error.
08Section

Rate limits

Per API key:

Per minute
100
requests / minute / key
Per hour
1,000
requests / hour / key

Exceeding either returns 429 RATE_LIMITED with retry_after_seconds in the body. The window slides — there's no fixed reset boundary. For bulk operations (e.g., provisioning 50 client workspaces), pace at ≤ 1 request/second to stay well under the limits.

Listing endpoints (currently only analytics) use cursor pagination: each response includes next_cursor and has_more. Pass next_cursor as ?cursor=… on the next call. has_more: false means you've reached the end.

bash
# Walk every page until done
CURSOR=""
while :; do
  URL="https://usepoplar.com/api/v1/analytics/posts?limit=100"
  [ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"
  RES=$(curl -s -H "Authorization: Bearer $KEY" "$URL")
  echo "$RES" | jq '.items'
  HAS_MORE=$(echo "$RES" | jq -r '.has_more')
  [ "$HAS_MORE" != "true" ] && break
  CURSOR=$(echo "$RES" | jq -r '.next_cursor')
done
10Section

Idempotency

POST /api/knowledge/items deduplicates on (workspace_id, type, metadata.external_id) when you supply external_id. Re-pushing the same triple updates the existing row instead of creating a duplicate. This is the only endpoint with built-in idempotency today; for others (POST /api/workspaces, POST /api/invitations/send) handle dedupe in your own client.

Need help?

Email support@usepoplar.com with your key prefix (the pop_live_4f2a8c… part — never the full key) and we'll debug. Or grab the OpenAPI spec to generate a client SDK.