REST API · v1

API Documentation

Programmatically manage your boards and pins via HTTPS. JSON in, JSON out. API access is part of the Enterprise plan.

Authentication

Generate an API key from Settings → API keys. Send it with every request as a Bearer token:

Authorization: Bearer ptp_live_xxxxxxxxxxxxxxxxxxxxxxxx

Each key has a set of scopes that limit what it can do:

  • boards:read — list your boards
  • boards:write — create / update boards
  • pins:read — read pins on your boards
  • pins:write — create / update / delete pins

Base URL

https://corkboard.social/api/public/v1

Boards

GET/api/public/v1/boards
scope: boards:read
List your boards. Newest first. Use ?limit=25 (max 100).
curl -H "Authorization: Bearer ptp_live_..." \
  https://corkboard.social/api/public/v1/boards?limit=25
{
  "boards": [
    {
      "id": "uuid", "title": "...", "slug": "...",
      "description": "...", "visibility": "public",
      "pins_count": 12, "links_count": 4,
      "views_count": 320,
      "created_at": "...", "updated_at": "..."
    }
  ]
}
POST/api/public/v1/boards
scope: boards:write
Create a new board.

Request body:

{
  "title": "My board",          // string, 1–120, required
  "description": "Optional",    // string, ≤500, optional
  "visibility": "public"        // "public" | "unlisted" | "private", default "public"
}
curl -X POST \
  -H "Authorization: Bearer ptp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"My board","visibility":"public"}' \
  https://corkboard.social/api/public/v1/boards

Response (201):

{
  "board": {
    "id": "uuid",
    "title": "My board",
    "slug": "my-board",
    "description": null,
    "visibility": "public",
    "created_at": "2026-06-21T10:00:00.000Z"
  }
}

Pins

GET/api/public/v1/pins?board_id=<uuid>
scope: pins:read
List pins on a board you own. Use &limit=50 (max 200).
curl -H "Authorization: Bearer ptp_live_..." \
  "https://corkboard.social/api/public/v1/pins?board_id=BOARD_UUID&limit=50"

Response (200):

{
  "pins": [
    {
      "id": "uuid",
      "board_id": "uuid",
      "slug": "pin-lxyz",
      "name": "My pin",
      "photo_url": "https://...",
      "short_desc": "Short text",
      "badges": ["new"],
      "pos_x": 120, "pos_y": 80, "rotation": 0,
      "pin_variant": "pin-01",
      "style": null,
      "likes_count": 0,
      "comments_count": 0,
      "created_at": "2026-06-21T10:00:00.000Z",
      "updated_at": "2026-06-21T10:00:00.000Z"
    }
  ]
}
POST/api/public/v1/pins
scope: pins:write
Create a new pin on one of your boards.

Request body:

{
  "board_id":   "BOARD_UUID",   // required
  "name":       "My pin",        // required, 1–120 chars
  "slug":       "my-pin",        // optional; auto-generated if omitted
  "photo_url":  "https://...",   // optional, string | null
  "short_desc": "Description",   // optional, ≤500 chars
  "badges":     ["new","hot"],   // optional, up to 12 strings
  "pos_x":      120,             // optional, number (default 0)
  "pos_y":      80,              // optional, number (default 0)
  "rotation":   0,               // optional, number (default 0)
  "pin_variant":"pin-01",        // optional, one of "pin-01" … "pin-10" (see Pin variants)
  "style":      { /* Pin style object — see Styles & widgets below */ }

}
curl -X POST \
  -H "Authorization: Bearer ptp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "board_id":"BOARD_UUID",
    "name":"My pin",
    "photo_url":"https://example.com/p.jpg",
    "badges":["new","featured"],
    "pos_x":120, "pos_y":80
  }' \
  https://corkboard.social/api/public/v1/pins

Response (201):

{
  "pin": {
    "id": "uuid",
    "board_id": "uuid",
    "slug": "my-pin",
    "name": "My pin",
    "photo_url": "https://example.com/p.jpg",
    "short_desc": null,
    "badges": ["new","featured"],
    "pos_x": 120, "pos_y": 80, "rotation": 0,
    "pin_variant": "pin-01",
    "style": null,
    "likes_count": 0,
    "comments_count": 0,
    "created_at": "2026-06-21T10:00:00.000Z",
    "updated_at": "2026-06-21T10:00:00.000Z"
  }
}
GET/api/public/v1/pins/:pinId
scope: pins:read
Fetch a single pin by id.
curl -H "Authorization: Bearer ptp_live_..." \
  https://corkboard.social/api/public/v1/pins/PIN_UUID

Response (200): { pin: Pin } — see schema below.

PATCH/api/public/v1/pins/:pinId
scope: pins:write
Partial update. Only send the fields you want to change; omit the rest.

Request body — any subset of:

{
  "name":        "Renamed",       // 1–120 chars
  "photo_url":   "https://...",   // string | null (send null to clear)
  "short_desc":  "New desc",      // string | null, ≤500 chars
  "badges":      ["sale"],        // replaces the array, up to 12
  "pos_x":       300,             // number
  "pos_y":       120,             // number
  "rotation":    15,              // number (degrees)
  "pin_variant": "pin-03",        // "pin-01" … "pin-10"
  "style":       { /* Pin style object, see below */ } // or null to clear

}
curl -X PATCH \
  -H "Authorization: Bearer ptp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Renamed","pos_x":300,"pos_y":120}' \
  https://corkboard.social/api/public/v1/pins/PIN_UUID

Response (200): { pin: Pin } — the full updated pin.

DELETE/api/public/v1/pins/:pinId
scope: pins:write
Delete a pin you own.
curl -X DELETE \
  -H "Authorization: Bearer ptp_live_..." \
  https://corkboard.social/api/public/v1/pins/PIN_UUID

Response (200):

{ "ok": true, "deleted": "PIN_UUID" }

What the API can not do

To keep boards fair, the public API is intentionally scoped to content you own. The following are not exposed via API — and we won't add them:

  • Likes / reactions on boards, pins, links or comments. Engagement counts can't be inflated through the API — they only change when real users interact through the app.
  • View counts (views_count, likes_count, comments_count) — read-only.
  • Follows, comments, votes, or notifications on other people's content.
  • Other users' boards, pins, or profile data — even if public. Use the regular website to browse those.
  • Admin / moderation actions (bans, reports, plan changes, audit logs).

If a request looks like an attempt to write to one of these, you'll get 403 or 404.

Pin variants, widgets & styles

Pin variants (pin_variant)
The visual shape/sticker of the pin. Pick one of these exact strings.
"pin-01" | "pin-02" | "pin-03" | "pin-04" | "pin-05"
"pin-06" | "pin-07" | "pin-08" | "pin-09" | "pin-10"

Default is "pin-01". Unknown values fall back to pin-01 when rendered.

style object — full schema
Controls paper texture, torn edges, attachment hardware, opacity, tint, the embedded widget, and explicit width/height. All fields optional; send only what you want to set.
{
  paper:      "plain" | "lined" | "grid" | "aged" | "kraft",     // default "plain"
                                                                  // "grid","aged","kraft" require Premium pin styles

  tornEdges:  { top: boolean, right: boolean,                     // default all false
                bottom: boolean, left: boolean },

  attachment: {
    type:  "pushpin" | "tape" | "clip" | "none",                  // default "pushpin"
                                                                  // "tape" & "clip" require Premium pin styles
    color: "#RRGGBB" | null                                       // hex, optional
  },

  opacity:    number,            // 0.9 – 1.0, default 1
  tint:       "#RRGGBB" | null,  // overlay tint, optional

  widget: {                      // optional embedded widget (see below)
    type: "image" | "text" | "youtube" | "spotify" | "audio"
        | "link"  | "quote" | "code"    | "map"     | "checklist",
    data: { /* widget-specific payload */ }
  } | null,

  width:  number | null,         // px, optional explicit size
  height: number | null
}

Plan-gated fields (premium paper textures, tape/clip attachments) are accepted in the request but will only render to viewers on a plan that allows them. The board owner's plan is what counts, not the API caller's.

Widget types — style.widget
Each widget has its own data shape. Send only the fields listed for that type.
// image — extra image inside the pin
{ type: "image",     data: { url: string, alt?: string } }

// text — rich text block
{ type: "text",      data: { content: string, align?: "left"|"center"|"right" } }

// youtube — embedded video
{ type: "youtube",   data: { videoId: string, start?: number } }

// spotify — embedded track/playlist/album
{ type: "spotify",   data: { url: string } }

// audio — direct audio file
{ type: "audio",     data: { url: string, title?: string } }

// link — preview card
{ type: "link",      data: { url: string, title?: string, description?: string, image?: string } }

// quote — pull quote
{ type: "quote",     data: { text: string, author?: string } }

// code — code snippet
{ type: "code",      data: { code: string, language?: string } }

// map — pinned location
{ type: "map",       data: { lat: number, lng: number, label?: string, zoom?: number } }

// checklist — interactive checklist
{ type: "checklist", data: { items: { text: string, checked?: boolean }[] } }

Some widget types (audio, code, map, checklist, embeds) are considered advanced widgets and require a plan with can_advanced_widgets. Unauthorized widgets are rejected with 400.

Example: create a pin with a YouTube widget
curl -X POST \
  -H "Authorization: Bearer ptp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "board_id":"BOARD_UUID",
    "name":"Demo video",
    "pos_x":200, "pos_y":160, "rotation":-3,
    "pin_variant":"pin-04",
    "style": {
      "paper":"lined",
      "attachment":{"type":"tape","color":"#facc15"},
      "tornEdges":{"top":false,"right":false,"bottom":true,"left":false},
      "widget":{
        "type":"youtube",
        "data":{"videoId":"dQw4w9WgXcQ","start":0}
      }
    }
  }' \
  https://corkboard.social/api/public/v1/pins

Schemas

Board
{
  id:           string  // uuid
  title:        string
  slug:         string
  description:  string | null
  visibility:   "public" | "unlisted" | "private"
  pins_count:   number
  links_count:  number
  views_count:  number
  created_at:   string  // ISO 8601
  updated_at:   string  // ISO 8601
}
Pin
{
  id:             string  // uuid
  board_id:       string  // uuid
  slug:           string
  name:           string
  photo_url:      string | null
  short_desc:     string | null
  badges:         string[]
  pos_x:          number
  pos_y:          number
  rotation:       number  // degrees
  pin_variant:    string  // "pin-01" … "pin-10"
  style:          PinStyle | null  // see Styles & widgets section
  likes_count:    number
  comments_count: number
  created_at:     string  // ISO 8601
  updated_at:     string  // ISO 8601
}
Error
{ "error": "Human-readable message" }

Errors & limits

Errors come back as { error: string } with these status codes:

  • 400 — invalid input
  • 401 — missing or invalid Bearer token
  • 403 — token missing required scope
  • 404 — resource not found, or you don't own it
  • 500 — unexpected server error

A key only sees resources owned by the user who created it. Revoked keys stop working immediately.