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>:
https://staplegun.app/demo/owl-and-anchor/week.png?size=storySwap week for tonight, next, month or upcoming; swap owl-and-anchor for hollow-pines, smoke-and-salt, basement-417 or blue-heron.
For your own calendar:
- 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.
- Use the image URLs.
staplegun.app/p/{board}/week.pngis this week’s poster, forever. It redraws itself when the calendar changes. - 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 anX-Edit-Keyheader. - Lost it?
POST /v1/boards/{id}/rotate-keyissues 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.
BASE=https://staplegun.app
BOARD=the-owl-and-anchor-x7k2
KEY=sg_your_edit_key03Image 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.
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
| View | What’s on it |
|---|---|
week | Every night this week — post it Monday. |
tonight | Tonight's bill — perfect for stories. Falls back to the next show when nothing is on tonight. |
next | The next thing on the calendar, big. |
month | The monthly calendar for the front window. |
upcoming | The next dates, whenever they are — tour posters. |
event | One 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
| size | Pixels | For |
|---|---|---|
post (default) | 1080×1350 | Instagram post — 4:5 feed post |
square | 1080×1080 | Square — 1:1 feed post |
story | 1080×1920 | Story / Reel — 9:16 stories, TikTok, reels |
og | 1200×630 | Link preview — Facebook, X, iMessage, Slack cards |
letter | 1275×1650 | Letter print — 8.5×11 in window poster |
tabloid | 1650×2550 | Tabloid print — 11×17 in show poster |
a4 | 1240×1754 | A4 print — 210×297 mm poster |
a3 | 1754×2480 | A3 print — 297×420 mm poster |
Parameters
| Param | Values | Notes |
|---|---|---|
size | see above | Defaults to post. |
template | marquee, showprint, handbill, xerox, supperclub | Override the board’s template for this one image. |
palette | palette id | One of the template’s color sets (table below). Unknown ids fall back to the template’s first set. |
offset | 0–12, next | week and month views: how many weeks/months ahead. next = 1. |
page | 1, 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. |
limit | 1–40 | upcoming view: how many dates. Default 8. |
event | event id | event view only. |
scale | 0.2–1 | PNG only. Smaller renders for thumbnails, e.g. scale=0.4. |
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:
| template | palette ids | Best for |
|---|---|---|
marquee | house, redline, ice | Rock clubs, dive bars, theaters |
showprint | hatch, bluegrass, kraft | Country & folk rooms, honky-tonks, breweries, touring bands |
handbill | signal, cobalt, acid | Indie & electronic venues, art spaces, food trucks, cafés |
xerox | copier, newsprint, blackout | DIY spaces, punk & hardcore rooms, basement shows, record stores |
supperclub | bottle, oxblood, midnight | Jazz 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.
{
"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.
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-x7k205JSON 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).
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"
}'{
"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).
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.
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.
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.
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 write | It 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 cover | price |
21+, 18+, All ages | age |
doors 8pm | doors |
SOLD OUT, CANCELLED, Postponed | A 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 truck | category and a small label, so trivia night doesn’t look like a band |
Private party, Closed for buyout | Hidden 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.
HTTP/1.1 401 Unauthorized
{ "error": "unauthorized", "message": "That edit key does not match this board." }| error | Status | Meaning |
|---|---|---|
bad_json | 400 | The body isn’t valid JSON. |
invalid | 400 | A field is missing or wrong; the message says which. |
bad_request | 400 | An image parameter is wrong (unknown view, size or template). |
bad_calendar | 400 | We couldn’t fetch or read that calendar link. |
unauthorized | 401 | Missing or wrong edit key. |
not_found | 404 | No board with that id. |
already_pro | 409 | Checkout on a board that’s already Pro. |
rate_limited | 429 | Too many requests — see below. |
email_unavailable | 503 | Email isn’t set up on this deployment. |
internal | 500 | Our 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.
| What | Limit |
|---|---|
| Fresh poster renders (image cache misses) | 400 an hour per IP |
POST /v1/render without a key | 120 an hour per IP |
POST /v1/calendar/preview | 40 an hour per IP |
POST /v1/boards | 20 an hour per IP |
PATCH /v1/boards/{id} | 600 an hour per board |
POST /v1/recover | 5 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:
<div data-staplegun="the-owl-and-anchor-x7k2"></div>
<script async src="https://staplegun.app/embed.js"></script>Options go on the same element:
| Attribute | Values | Notes |
|---|---|---|
data-theme | auto, light, dark | auto (default) follows the visitor’s light/dark setting. |
data-limit | a number | How many upcoming events to list. |
data-accent | a color, e.g. #ff4b1f | Dates, links and highlights. Match your site. |
<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
- Trigger: Schedule by Zapier → Every Week → Monday, 9am.
- Action: Webhooks by Zapier → GET
https://staplegun.app/b/the-owl-and-anchor-x7k2/caption.txt?view=weekfor the caption. - 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.
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.