For developers · API v1

The docs

Every poster is a URL. Every board is a small JSON API guarded by one key. Feeds are plain JSON, iCal and text. That’s the whole thing — here it is with examples you can paste.

01Quick start

You don’t need a key to see how it works. This is a live poster for one of our fictional sample boards — open it, paste it into Slack, put it in an <img>:

A sample poster, as a story
https://staplegun.app/demo/owl-and-anchor/week.png?size=story

Swap week for tonight, next, month or upcoming; swap owl-and-anchor for hollow-pines, smoke-and-salt, basement-417 or blue-heron.

The sample poster: this week at The Owl & Anchor, story size.

For your own calendar:

  1. Make a board at /new (or with POST /v1/boards). Point it at a calendar link or type your shows in. You get a board id and a secret edit key.
  2. Use the image URLs. staplegun.app/p/{board}/week.png is this week’s poster, forever. It redraws itself when the calendar changes.
  3. Paste them anywhere that takes an image URL: your scheduler, your website, an email template, a TV in the bar.

02Boards & edit keys

A board is one calendar plus its look: name, time zone, template, colors. There are no accounts. Creating a board returns an edit key (sg_…) exactly once — we only store a hash of it.

  • The edit link is staplegun.app/edit/{board}/{key}. Anyone with it can change the board, so keep it private.
  • The same key authenticates the API. Send it as Authorization: Bearer sg_… (preferred) or an X-Edit-Key header.
  • Lost it? POST /v1/boards/{id}/rotate-key issues a new one (the old one stops working). If the board has an email, /recover emails fresh links.
  • Everything under /p/ and /b/ is public and needs no key.
Shell variables used below
BASE=https://staplegun.app
BOARD=the-owl-and-anchor-x7k2
KEY=sg_your_edit_key

03Image URLs

GET/p/{board}/{view}.{png|svg}

PNG for posting, SVG for print and for web pages (SVGs are a fraction of the size). Free boards carry a small “made with staplegun.app” imprint; Pro boards don’t, and get their logo and custom colors. CORS is open, so you can fetch these from a browser.

Examples
https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png                     # this week, 4:5 post
https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png?size=story          # this week, 9:16 story
https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png?offset=next         # next week
https://staplegun.app/p/the-owl-and-anchor-x7k2/tonight.png?size=story       # tonight's bill
https://staplegun.app/p/the-owl-and-anchor-x7k2/next.png?size=og             # link-preview card
https://staplegun.app/p/the-owl-and-anchor-x7k2/month.svg?size=tabloid       # 11×17 print poster
https://staplegun.app/p/the-owl-and-anchor-x7k2/upcoming.png?limit=12        # next 12 dates (tours)

Views

ViewWhat’s on it
weekEvery night this week — post it Monday.
tonightTonight's bill — perfect for stories. Falls back to the next show when nothing is on tonight.
nextThe next thing on the calendar, big.
monthThe monthly calendar for the front window.
upcomingThe next dates, whenever they are — tour posters.
eventOne event, one poster. Needs ?event={id} (ids come from events.json).

Nights roll over at 4am local time, so a 1am set on Saturday still counts as Friday night. Weeks start on Monday unless the board says Sunday.

Sizes

sizePixelsFor
post (default)1080×1350Instagram post — 4:5 feed post
square1080×1080Square — 1:1 feed post
story1080×1920Story / Reel — 9:16 stories, TikTok, reels
og1200×630Link preview — Facebook, X, iMessage, Slack cards
letter1275×1650Letter print — 8.5×11 in window poster
tabloid1650×2550Tabloid print — 11×17 in show poster
a41240×1754A4 print — 210×297 mm poster
a31754×2480A3 print — 297×420 mm poster

Parameters

ParamValuesNotes
sizesee aboveDefaults to post.
templatemarquee, showprint, handbill, xerox, supperclubOverride the board’s template for this one image.
palettepalette idOne of the template’s color sets (table below). Unknown ids fall back to the template’s first set.
offset0–12, nextweek and month views: how many weeks/months ahead. next = 1.
page1, 2, …Busy lists don’t shrink to unreadable — they paginate. The response header X-Staplegun-Pages says how many pages there are; request each for a carousel.
limit1–40upcoming view: how many dates. Default 8.
eventevent idevent view only.
scale0.2–1PNG only. Smaller renders for thumbnails, e.g. scale=0.4.
Carousel: how many pages?
curl -sI "$BASE/p/$BOARD/month.png?size=story" | grep -i x-staplegun
# X-Staplegun-Pages: 2
# X-Staplegun-Page: 1
curl -o month-2.png "$BASE/p/$BOARD/month.png?size=story&page=2"

Templates and their color sets:

templatepalette idsBest for
marqueehouse, redline, iceRock clubs, dive bars, theaters
showprinthatch, bluegrass, kraftCountry & folk rooms, honky-tonks, breweries, touring bands
handbillsignal, cobalt, acidIndie & electronic venues, art spaces, food trucks, cafés
xeroxcopier, newsprint, blackoutDIY spaces, punk & hardcore rooms, basement shows, record stores
supperclubbottle, oxblood, midnightJazz clubs, wine bars, supper clubs, hotel lounges, restaurants with live music

Caching & freshness

  • We re-read a board’s calendar about every ten minutes. Editing the board (or hitting refresh) takes effect right away.
  • Images are sent with Cache-Control: public, max-age=300, so a browser may hold one for five minutes.
  • Some schedulers and chat apps cache images by URL for much longer. Add a throwaway parameter that changes once per post — e.g. &v=2026-10-05 — to force a fresh copy. Unknown parameters are ignored.
  • If a poster can’t be drawn (bad parameter, unknown board), you still get an image — an SVG explaining what went wrong — with a 4xx status, so a broken link never shows up as a blank square.

04Feeds

Public, CORS-enabled, cached for five minutes.

GET/b/{board}/events.json

Upcoming events with the parser’s work already done. ?limit= up to 200 (default 50). Free boards include an attribution object; please show it if you build your own listing.

Response (trimmed)
{
  "board": { "id": "the-owl-and-anchor-x7k2", "name": "The Owl & Anchor", "tz": "America/Chicago", "pro": false, "url": "https://staplegun.app/b/the-owl-and-anchor-x7k2" },
  "events": [{
    "id": "k3j9x1lq8",
    "title": "Loose Teeth (Record Release) w/ Sister Cathedral — $12 — 18+ — SOLD OUT",
    "headliner": "Loose Teeth",
    "support": ["Sister Cathedral"],
    "start": "2026-10-03T02:30:00.000Z",
    "end": null,
    "price": "$12", "age": "18+", "doors": null,
    "note": "Record release", "category": "music", "kicker": null,
    "soldOut": true, "cancelled": false,
    "location": null, "city": null, "url": null,
    "page": "https://staplegun.app/b/the-owl-and-anchor-x7k2/e/k3j9x1lq8",
    "poster": "https://staplegun.app/p/the-owl-and-anchor-x7k2/event.png?event=k3j9x1lq8"
  }],
  "attribution": { "text": "Listings by Staplegun", "url": "https://staplegun.app/?ref=the-owl-and-anchor-x7k2" }
}

GET/b/{board}/calendar.ics

A subscribe-able iCal feed of the board — handy when the board was typed in by hand rather than fed from a calendar.

GET/b/{board}/caption.txt?view=week

A ready-to-paste caption that matches the poster. Takes the same view, offset, limit and event parameters as the images.

caption.txt
This week at The Owl & Anchor (Sep 28 – Oct 4)

Mon 9/28 — Open Mic Night — sign-ups at 7 · 7:30pm · Free
Thu 10/1 — Glass Harbor · 9pm · $18 · 21+
Fri 10/2 — The Groans w/ Mothwing · 9pm · $12
    Loose Teeth w/ Sister Cathedral · 9:30pm · $12 · 18+ · SOLD OUT

Full calendar: https://staplegun.app/b/the-owl-and-anchor-x7k2

05JSON API

Base URL https://staplegun.app/v1. Requests and responses are JSON. Endpoints marked edit key need the board’s key.

GET/v1/templates

Templates (with blurbs and palettes), views, sizes and board kinds — everything a picker UI needs.

POST/v1/boards

Create a board from a calendar link or a list of events. name is required; kind is one of venue, band, truck, series; tz is an IANA zone. Optional: template, paletteId, tagline, footer, email (for recovery), clock (12h/24h), weekStart (0 Sunday, 1 Monday).

Request
curl -X POST "$BASE/v1/boards" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "The Owl & Anchor",
    "kind": "venue",
    "tz": "America/Chicago",
    "calendarUrl": "https://calendar.google.com/calendar/ical/…/public/basic.ics",
    "template": "marquee",
    "email": "booking@owlandanchor.example"
  }'
201 Created
{
  "board": { "id": "the-owl-and-anchor-x7k2", "name": "The Owl & Anchor", "pro": false, … },
  "editKey": "sg_…",          // shown once — store it
  "links": {
    "public": "https://staplegun.app/b/the-owl-and-anchor-x7k2",
    "edit": "https://staplegun.app/edit/the-owl-and-anchor-x7k2/sg_…",
    "images": {
      "week": "https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png",
      "weekStory": "https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png?size=story",
      "tonight": "https://staplegun.app/p/the-owl-and-anchor-x7k2/tonight.png?size=story",
      "monthPrint": "https://staplegun.app/p/the-owl-and-anchor-x7k2/month.svg?size=tabloid",
      …
    },
    "feeds": { "events": "…/events.json", "ics": "…/calendar.ics", "caption": "…/caption.txt?view=week" },
    "embed": "<div data-staplegun=…></div><script async src=…/embed.js></script>"
  }
}

GET/v1/boards/{id}

Public fields, or every field when you send the key. Always includes links.

PATCH/v1/boards/{id}edit key

Send only what changes: name, kind, tagline, footer, tz, clock, weekStart, template, paletteId, events (replaces the manual list), calendarUrl (null switches to manual), showPrivate, email, digest (the weekly “your posters are ready” email). Pro boards also use customPalette {bg, ink, accent, muted?} and logo (a PNG/JPEG data URI under ~300 KB).

Switch template and color set
curl -X PATCH "$BASE/v1/boards/$BOARD" \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "template": "xerox", "paletteId": "newsprint" }'

DELETE/v1/boards/{id}edit key

Deletes the board. Its image URLs stop working immediately.

POST/v1/boards/{id}/refreshedit key

Re-reads the calendar now instead of waiting up to ten minutes. Returns {ok, error, events, version}.

POST/v1/boards/{id}/rotate-keyedit key

Returns a new editKey and links. The old key and edit link stop working.

GET/v1/boards/{id}/events

Parsed events, like events.json but with optional from/to (ISO 8601) and, with the key, private events too. Includes error and stale if the calendar couldn’t be fetched.

POST/v1/render

Draw a poster from events you send — no board needed. Takes the image parameters (view, size, template, palette, page, offset, limit, scale), plus format (png/svg), a board object (name, kind, tz, tagline, footer) and now to pretend it’s another day. Responds with the image itself.

Request → image
curl -X POST "$BASE/v1/render" \
  -H 'Content-Type: application/json' \
  -o poster.png \
  -d '{
    "view": "week", "size": "story", "template": "handbill",
    "board": { "name": "Smoke & Salt BBQ", "kind": "truck", "tz": "America/Chicago" },
    "events": [
      { "title": "Congress Ave Lunch Row", "start": "2026-10-06T11:00:00-05:00",
        "end": "2026-10-06T14:00:00-05:00", "location": "Congress Ave & 5th St, Austin" },
      { "title": "Hopfield Brewing", "start": "2026-10-08T17:00:00-05:00" }
    ]
  }'

Without a key, renders are rate limited and carry the imprint. With a Pro board’s key and ?board={id}, there’s no imprint, the board’s logo is added, and you can pass colors {bg, ink, accent, muted?}.

POST/v1/calendar/preview

Check a calendar link before you save it. Accepts Google, iCloud (webcal:// is fine) and Outlook links, or any .ics URL.

Request / response
curl -X POST "$BASE/v1/calendar/preview" \
  -H 'Content-Type: application/json' \
  -d '{ "url": "webcal://p52-caldav.icloud.com/published/2/…", "tz": "America/Chicago" }'

{
  "url": "https://p52-caldav.icloud.com/published/2/…",
  "calendarName": "Shows", "calendarTz": "America/Chicago",
  "total": 41, "upcoming": 12,
  "sample": [{
    "title": "Glass Harbor — $18 — 21+", "headliner": "Glass Harbor", "support": [],
    "start": "2026-10-02T02:00:00.000Z", "price": "$18", "age": "21+", "category": "music"
  }]
}

POST/v1/quickadd

Turn a pasted list — one show per line, loose formats welcome — into events. Set dayFirst for 3/10 = 3 October. Lines it can’t date come back in skipped.

Request
curl -X POST "$BASE/v1/quickadd" \
  -H 'Content-Type: application/json' \
  -d '{ "tz": "America/Chicago", "text": "Fri 10/3 9pm Hollow Pines w/ The Marrow Kings $12\nOct 4 — Rosewater Hall, Dallas TX\nSaturday @ 8 — Loose Teeth" }'

POST/v1/recover

{email} — emails fresh edit links for every board with that address. Always answers the same way, so it can’t be used to look people up. Returns 503 email_unavailable if email isn’t configured on this deployment.

Billing

POST/v1/billing/checkoutedit key with {boardId, interval: "month" | "year"} returns a Stripe Checkout url. POST/v1/billing/portaledit key returns a Customer Portal url for card changes, invoices and cancelling.

06Events & the parser

An event needs a title and a start (ISO 8601 or epoch ms). Everything else is optional: end, allDay, location, url, description. Staplegun reads the title the way a person would:

You writeIt becomes
w/, with, feat., +, /Support acts, split on commas. (& and and don’t split — too many bands have them.)
$12, $10 adv / $15 door, Free, No coverprice
21+, 18+, All agesage
doors 8pmdoors
SOLD OUT, CANCELLED, PostponedA stamp on the poster
(Record Release), Matinee, Early show, Saturday Matinee: …note, shown under the name
Trivia, karaoke, open mic, comedy, drag, bingo, DJ, brunch, tasting, market, film, food truckcategory and a small label, so trivia night doesn’t look like a band
Private party, Closed for buyoutHidden from posters and feeds unless showPrivate is on

Details in the calendar description (price, age, doors, a ticket link) are picked up too. If you already have structured data, send headliner, support, price, age, doors, note, soldOut or cancelled and the parser leaves those fields alone.

07Errors

JSON endpoints fail with a 4xx/5xx status and a body of { "error": code, "message": sentence }. The message is written for humans — fine to show as-is.

Example
HTTP/1.1 401 Unauthorized
{ "error": "unauthorized", "message": "That edit key does not match this board." }
errorStatusMeaning
bad_json400The body isn’t valid JSON.
invalid400A field is missing or wrong; the message says which.
bad_request400An image parameter is wrong (unknown view, size or template).
bad_calendar400We couldn’t fetch or read that calendar link.
unauthorized401Missing or wrong edit key.
not_found404No board with that id.
already_pro409Checkout on a board that’s already Pro.
rate_limited429Too many requests — see below.
email_unavailable503Email isn’t set up on this deployment.
internal500Our fault. Try again; tell us if it sticks.

08Rate limits

Fixed one-hour windows. Image and feed GETs served from cache aren’t counted; only fresh renders are, which is why you shouldn’t put a random cache-buster on every request.

WhatLimit
Fresh poster renders (image cache misses)400 an hour per IP
POST /v1/render without a key120 an hour per IP
POST /v1/calendar/preview40 an hour per IP
POST /v1/boards20 an hour per IP
PATCH /v1/boards/{id}600 an hour per board
POST /v1/recover5 an hour per IP

Over the limit you’ll get 429 rate_limited. Doing something that needs more? Write to hello@staplegun.app.

09Embed widget

An always-current list of upcoming shows for your own website. Paste this where the list should go:

HTML
<div data-staplegun="the-owl-and-anchor-x7k2"></div>
<script async src="https://staplegun.app/embed.js"></script>

Options go on the same element:

AttributeValuesNotes
data-themeauto, light, darkauto (default) follows the visitor’s light/dark setting.
data-limita numberHow many upcoming events to list.
data-accenta color, e.g. #ff4b1fDates, links and highlights. Match your site.
Dark site, ten shows, brand color
<div data-staplegun="the-owl-and-anchor-x7k2"
     data-theme="dark" data-limit="10" data-accent="#ffb429"></div>
<script async src="https://staplegun.app/embed.js"></script>

It reads events.json, so it’s never more than a few minutes behind your calendar. Free boards show a small “Listings by Staplegun” credit. Rather build your own? Fetch events.json — CORS is open.

10Automation recipes

The trick is always the same: a scheduler fetches a poster URL at posting time, so it gets whatever the calendar says that morning. Add a date parameter so nothing serves last week’s copy.

Zapier: story every Monday

  1. Trigger: Schedule by Zapier → Every Week → Monday, 9am.
  2. Action: Webhooks by Zapier → GET https://staplegun.app/b/the-owl-and-anchor-x7k2/caption.txt?view=week for the caption.
  3. Action: your scheduler (Buffer, Later, Hootsuite…) → create a post with image URL https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png?size=story&v={{zap_meta_human_now}} and the caption from step 2.

Make

Set the scenario to run weekly, then: HTTP → Make a request for caption.txt → HTTP → Get a file for week.png → your Instagram, Facebook or Buffer module, mapping in the file and the caption.

Meta Business Suite, by hand

Open week.png on your phone, save the image, and paste the caption from caption.txt. Thirty seconds on a Monday.

Print

https://staplegun.app/p/the-owl-and-anchor-x7k2/month.svg?size=tabloid (11×17 in) or size=a3 is a vector file any copy shop can print sharp. Use offset=next to print next month’s before it starts.

A TV behind the bar

Point any “show a web image” signage app at https://staplegun.app/p/the-owl-and-anchor-x7k2/week.png?size=og — landscape, and it refreshes itself.

Missing something? The machine-readable version is at /v1/openapi.json, and a person answers hello@staplegun.app.