Flappy Board API v2.0

Build on the board.

A local HTTPS REST API is running on every Flappy Board device. Send messages, configure the layout, upload images — from a shell script, a home automation rule, or a full application.

🔑
Requires Amateur tier or above. Purchase in-app to unlock the API server, bearer token, and controller pairing.
🔒
HTTPS on port 8443 with a self-signed certificate. Use certificate pinning or curl -k for quick testing.

Overview

Getting Started

Everything you need to make your first API call in under two minutes.

1
Unlock the API
Purchase the Amateur tier from within Flappy Board. The API server starts automatically after purchase and survives app restarts.
2
Find your device IP & token
Open Flappy Board Settings on the device running the board. On iOS/iPadOS, long-press the board to open Settings. On tvOS, press Play/Pause. The Settings screen shows your device's local IP address, bearer token, and TLS certificate fingerprint.
3
Smoke test
Ping the status endpoint — no auth required.
shell
curl -k https://<DEVICE_IP>:8443/v1/status # Response { "status": "running" }
4
Send your first message
Include your bearer token in the Authorization header.
shell
curl -k -X POST https://<DEVICE_IP>:8443/v1/message \ -H "Authorization: Bearer <TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "message": { "rows": { "items": [ [{"text": "HELLO"}], [{"text": "WORLD"}] ] } } }'
TLS & Certificate Pinning

The server uses a self-signed TLS certificate generated on first launch. For quick scripting, pass -k (curl) or equivalent to skip verification. For production clients, pin the certificate's SHA-256 fingerprint shown in Settings.

The example Python client on GitHub demonstrates fingerprint pinning via a custom SSLContext.

Base URL

All endpoints are relative to:

text
https://<DEVICE_IP>:8443/v1

Security

Authentication

Most endpoints require a Bearer token. Two endpoints — GET /v1/status and GET /v1/fonts — are public.

Include the token in every protected request:

http
Authorization: Bearer <TOKEN>

The token is a long random string shown in Flappy Board Settings. It does not rotate unless you reset the server in Settings.

Error Responses
401 Missing or invalid bearer token. Check Authorization header.
402 Tier upgrade required. Response includes minimum_tier field indicating which tier unlocks the feature.
json — 402 response
{ "error": "upgrade_required", "minimum_tier": "professional" }

Pricing

Tiers & API Access

The API is gated by in-app purchase tier. The free tier runs the display but has no API access.

Feature Free Amateur Professional
Authentic flip animation
Built-in demos
Color themes
Local HTTPS API (all endpoints)
Controller app pairing
Home Assistant integration
Transition animations (16 modes)
Custom header/footer images
Image library (column type)
Board branding & custom fonts

API Reference

Endpoints

All requests and responses use JSON. Protected endpoints return 401 on a missing or invalid token. A machine-readable OpenAPI spec is also available for code generation and tooling.

GET /v1/status

Health check. Returns the running state of the API server. No authentication required.

Response — 200
json
{ "status": "running" }
GET /v1/fonts

Returns all fonts installed on the device, grouped by family. Use the returned font names in PUT /v1/config or the per-message font field. No authentication required.

Response — 200
json
{ "families": [ { "family": "Helvetica Neue", "fonts": ["HelveticaNeue", "HelveticaNeue-Bold", "HelveticaNeue-Italic"] }, ... ] }
POST /v1/message Bearer

Queues a message to be displayed on the board. Rows are applied in order — any row not included keeps its current content. Per-message overrides for font, charset, flip_speed, and transition_animation apply for this message only and do not persist.

Request Body
FieldTypeDescription
message object required The message body. Contains a rows object.
message.rows.items array[][] required 2D array of cells — outer array is rows, inner array is one cell per text/image column. See Message Format.
message.rows.vertical_alignment string optional top | center | bottom. Overrides board default for this message.
font string optional Font name override for this message. Use GET /v1/fonts for valid names.
flip_speed number optional Speed multiplier. 1.0 = normal, 2.0 = twice as fast. Applies to this message only.
charset string[] optional Override character set for this message. Each item is a single character.
transition_animation string optional Animation mode for this message. See Transition Animations. Requires Professional tier.
Example — simple message
shell
curl -k -X POST https://<DEVICE_IP>:8443/v1/message \ -H "Authorization: Bearer <TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "message": { "rows": { "items": [ [{"text": "GATE 12"}], [{"text": "NOW BOARDING"}] ], "vertical_alignment": "center" } } }'
Example — multi-column with color
json
{ "message": { "rows": { "items": [ [ { "text": "12:34", "alignment": "right" }, { "text": "CHICAGO" }, { "text": "<color='#5ecf8a'>ON TIME</color>" } ], [ { "text": "13:00", "alignment": "right" }, { "text": "NEW YORK" }, { "text": "<color='red'>+15 MIN</color>" } ] ] } }, "transition_animation": "staggered_start" }
Response codes
200Message accepted and queued. May include warnings array for truncation or charset substitutions.
400Invalid request — malformed JSON, unavailable font, empty charset, or body exceeding 64 KB.
401Missing or invalid bearer token.
402Tier upgrade required (e.g. transition_animation requires Professional).
GET /v1/board Bearer

Returns a snapshot of every cell currently displayed on the board — the character and color shown at the moment of the request. Useful for pre-filling a grid editor before composing a new message.

Response — 200
json
{ "rows": [ [ { "text": "H" }, { "text": "E", "color": "#FF6600" }, { "text": "L" }, { "text": "L" }, { "text": "O" } ] ] }

The outer array is rows (top to bottom), the inner array is columns (left to right). Dimensions match the board's configured rows and cols. Cells using the board's default color omit the color field.

DELETE /v1/board Bearer

Clears all cells on the board, returning it to a blank state.

Response — 200
json
{ "status": "accepted" }
GET /v1/config Bearer

Returns the full persistent board configuration. The response can be round-tripped back to PUT /v1/config to restore a saved state.

Response — 200
json
{ "show_startup_hint": true, "show_debug_overlay": false, "disable_settings_ui": false, "board": { "background_color": "#0D0D0D", "header": { "image": "", "height": 0, "alignment": "center" }, "body": { "rows": 6, "cols": 22, "charset_default": [" ", "A", "B", "..."], "font_default": { "name": "Helvetica Neue", "color": "#FFF2CC" }, "flap_color": "#141414", "row_spacing_ratio": 0.15, "column_spacing_pts": 6, "flip_speed": 1.0, "horizontal_alignment_default": "left", "vertical_alignment_default": "top", "flip_easing_threshold": 8, "flip_easing_max_speedup": 3.0, "transition_animation": "staggered_start", "columns": [] }, "footer": { "image": "", "height": 0, "alignment": "center" } } }
PUT /v1/config Bearer

Updates the persistent board configuration. All fields are optional — only supplied fields are changed. Changes take effect immediately on the live display and persist across app restarts.

Sending columns: [] returns the board to single-column mode. Sending an array with 2+ entries enables multi-column mode and the effective board width becomes the sum of all column widths.
Example — change colors
json
{ "board": { "background_color": "#1A0A00", "body": { "flap_color": "#2A1500", "font_default": { "color": "#FFB347" } } } }
Example — 3-column departure layout
json
{ "board": { "body": { "columns": [ { "width": 5, "type": "text", "horizontal_alignment_override": "right" }, { "width": 1, "type": "gap" }, { "width": 14, "type": "text" }, { "width": 1, "type": "gap" }, { "width": 5, "type": "text" } ] } } }
Example — lock settings UI for a kiosk deployment
json
{ "disable_settings_ui": true }
Response codes
200Configuration applied immediately.
400Invalid value — unavailable font, invalid hex color, out-of-range rows, or invalid column definition.
401Missing or invalid bearer token.
402Tier upgrade required.
DELETE /v1/config Bearer

Resets all persistent board configuration to the adaptive defaults for the host device (rows, cols, charset, font, colors, flip speed, columns, header/footer images). Use GET /v1/config first to capture the current state before resetting.

Response — 202
json
{ "status": "accepted" }
GET /v1/images Bearer

Returns the list of labels for all images in the image library. Pass ?include_data=true to include base64 image data.

Response — 200
json
{ "labels": ["AA", "DL", "UA"] }
POST /v1/images Bearer

Uploads a PNG or JPEG image and associates it with a label. Labels are normalized to uppercase alphanumeric (A–Z, 0–9, -, _) before storage. Maximum image size is 5 MB. Uploading with an existing label replaces it.

Request Body
FieldTypeDescription
label string required 1–10 chars, letters/digits/-/_. Normalized to uppercase on storage.
image string required Base64-encoded PNG or JPEG.
background_color string optional #RRGGBB fill color for letterboxed areas. Defaults to flap background color.
Example
shell
# Encode a PNG and upload it IMAGE=$(base64 -i logo.png) curl -k -X POST https://<DEVICE_IP>:8443/v1/images \ -H "Authorization: Bearer <TOKEN>" \ -H "Content-Type: application/json" \ -d "{\"label\": \"AA\", \"image\": \"$IMAGE\", \"background_color\": \"#F0F0F0\"}"
Response — 200
json
{ "status": "accepted", "label": "AA", "background_color": "#F0F0F0" }
DELETE /v1/images/{label} Bearer

Removes the image associated with {label} from the library. Returns accepted even if the label did not exist.

Response — 200
json
{ "status": "accepted" }

Guide

Message Format

The message payload uses a 2D array of cells that maps onto the board's row and column structure.

Single-Column Mode

When the board has no custom column layout, the entire display is one full-width text area. Each row in items has exactly one cell:

json
"items": [ [{ "text": "GATE 12" }], // row 1 [{ "text": "BOARDING" }] // row 2 ]
Multi-Column Mode

When the board has a custom column layout (set via PUT /v1/config), each row contains one cell per text or image column. Gap columns are defined in the config but are not referenced in message rows:

json — 3 text columns, gaps between them
"items": [ [ { "text": "12:34", "alignment": "right" }, // col 0: time { "text": "CHICAGO" }, // col 1: destination { "text": "ON TIME" } // col 2: status ] ]

Gap columns are skipped automatically — you only provide cells for text and image columns, in column order.

Vertical Alignment

Control where the rows sit within the board's full height. Useful when sending fewer rows than the board's configured row count:

json
"rows": { "items": [[{ "text": "DELAYED" }]], "vertical_alignment": "center" // top | center | bottom }

Guide

Color & Style Tags

Inline tags within cell text values let you color characters and control vertical position within each cell.

Color Tags

Use named colors or hex values:

text — inside a "text" field value
<color='red'>DELAYED</color> <color='#5ecf8a'>ON TIME</color> <color='#FF6600'>GATE</color> 12
Superscript & Subscript

Render characters in the top or bottom half of their cell:

text
14<super><color='cyan'>:32</color></super> <sub>GATE</sub>

<color> tags can nest inside <super>/<sub> to combine color and position.

Guide

Transition Animations

16 animation modes control how cells flip when a new message arrives. Pass transition_animation in POST /v1/message for a one-off override, or set it in PUT /v1/config to make it the board default. Requires Professional tier.

Professional tier required. Sending transition_animation on an Amateur or Free device returns HTTP 402 with minimum_tier: "professional".
ValueDescription
noneAll cells flip simultaneously with no stagger.
staggered_startRows flip sequentially top to bottom, each row delayed 0.5 s.
top_to_bottomLike staggered_start, but each cell spins through the full charset before landing.
bottom_to_topRows sweep sequentially from the bottom upward, each cell doing 3 full charset spins.
left_to_rightColumns sweep left to right, each cell doing 3 full charset spins.
right_to_leftColumns sweep right to left, each cell doing 3 full charset spins.
row_by_rowReading-order sweep (left→right, top→bottom), each cell doing 2 full charset spins.
dissolveEach cell starts at a random time within a ~12 s window, each doing 2 full charset spins.
diagonalNW→SE diagonal sweep; cells on the same diagonal fire together with 1 full spin.
middle_outCenter columns fire first, expanding outward to the edges, each cell doing 1 full spin.
outside_inEdge columns fire first, converging inward to the center, each cell doing 1 full spin.
spiralClockwise spiral from the upper-left corner, 1 full spin per cell.
hatchAlternating rows sweep from opposite sides (even rows right→left, odd rows left→right).
snake_upBoustrophedon sweep from bottom-right upward, 2 full spins per cell.
matrixEach column gets a random start offset and sweeps top to bottom, 3 full spins per cell.
randomPicks one of the above (except none) at random each time.

Guide

Multi-Column Layouts

Configure the board as a grid of named columns — text, gap, and image — to build departure boards, scoreboards, and dashboards.

Column Types
TypeDescription
text A standard text column. Receives cells from message rows.
gap An empty spacer column. Renders the raw board background with no flap tile. Not referenced in message rows.
image Displays an uploaded image from the image library. Each row cell's text is the image label (e.g. "AA" for American Airlines). Requires Professional tier.
Column Definition Fields
FieldTypeDescription
width integer required Width in character units. Sum of all column widths = effective board width.
type string required text | gap | image
horizontal_alignment_override string optional Default alignment for this column: left | center | right.
font_override.color string optional Default character color for this column as #RRGGBB. Overrides board default.
charset_override string[] optional Override charset for this column. For image columns, this is the list of valid labels (used for validation).
Example — Airline Departure Board with Logos
json — PUT /v1/config
{ "board": { "body": { "columns": [ { "width": 3, "type": "image", "charset_override": ["AA", "DL", "UA"] }, { "width": 1, "type": "gap" }, { "width": 4, "type": "text", "horizontal_alignment_override": "right" }, { "width": 1, "type": "gap" }, { "width": 10, "type": "text" }, { "width": 1, "type": "gap" }, { "width": 7, "type": "text" } ] } } }
json — POST /v1/message
{ "message": { "rows": { "items": [ [ { "text": "AA" }, // image column — AA logo { "text": "10:15" }, // time { "text": "LONDON" }, // destination { "text": "GATE 22" } // gate ] ] } } }

Guide

Board Configuration

The BoardConfig object controls every aspect of the display. All fields are optional in PUT /v1/config — omit any field to leave it unchanged.

Top-Level Fields
FieldTypeDefaultDescription
show_startup_hint boolean true Show the on-device usage hint at launch. Clears automatically after 10 s.
show_debug_overlay boolean false Show FPS and flip-speed overlay in the top-right corner.
disable_settings_ui boolean false Hide on-device Settings UI. Useful for kiosk deployments — API access is unaffected.
board.body Fields
FieldTypeDescription
rowsinteger 1–50Number of rows on the board.
colsinteger 1–40Column count in single-column mode. Ignored when multi-column columns array is active.
charset_defaultstring[]Ordered character set the display can render. Characters not in this set are replaced with a space.
font_default.namestringDefault font. Use GET /v1/fonts for valid names.
font_default.colorstringDefault character color as #RRGGBB.
flap_colorstringCell/flap background color as #RRGGBB.
flip_speednumberSpeed multiplier relative to the 0.24 s base flip duration. Higher = faster.
row_spacing_rationumberGap between rows as a ratio of cell height. 0.0 = no gap, 0.15 = 15% gap.
column_spacing_ptsnumberHorizontal gap between cells in points.
horizontal_alignment_defaultstringleft | center | right. Default for all cells when no column or cell override is set.
vertical_alignment_defaultstringtop | center | bottom. Default vertical placement for messages.
flip_easing_thresholdintegerMinimum flip steps to activate sin-bell easing. 0 = always ease.
flip_easing_max_speedupnumber ≥ 1.0Peak speed multiplier at the midpoint of a long sequence. 1.0 disables easing.
transition_animationstringDefault animation mode for incoming messages. See Transition Animations.
columnsarrayColumn layout definitions. Empty array returns to single-column mode.
board.header / board.footer Fields

Both header and footer support branding images. Requires Professional tier.

FieldTypeDescription
imagestringBase64 PNG or JPEG. Send "" to clear. Omit to leave unchanged. Max 10 MB decoded.
heightnumberHeight in points of the reserved image band. 0 = disabled.
alignmentstringleft | center | right.

Guide

Image Library

Upload logos or icons to use in image-type columns. Images are stored on the device and referenced by label in message rows.

1
Upload the image
Base64-encode a PNG or JPEG and POST it with a label. Labels are normalized to uppercase: "aa""AA". Set background_color to control the letterbox fill color when the image doesn't fill the full cell area.
2
Configure an image column
Add a column with type: "image" in PUT /v1/config. Set charset_override to the list of valid labels for validation.
3
Reference the label in messages
In message rows, use the label as the cell's text value. The board renders the stored image, scaled to fit the cell area.
Image columns and the image library require Professional tier. Header and footer images in PUT /v1/config also require Professional tier.

Examples

Integration Examples

Common patterns for controlling Flappy Board from scripts, applications, and home automation.

GeneralFuturics/FlappyBoardExampleClients — ready-to-run Python scripts
A collection of example clients covering common use cases. Each script uses uv inline dependencies — no virtualenv needed. All scripts read board connection details from ~/.config/flappyboard/config.json.
flappyboard.py Core CLI layout.py LLM formatter weather.py Open-Meteo flights.py Departures board trains.py Amtrak arrivals clock.py Live clock bluesky.py Bluesky feed commits.py GitHub commits pagerduty.py PagerDuty incidents slack.py Slack digest schedule.py macOS Calendar
Quick Message — curl
shell
HOST="192.168.1.42" TOKEN="your-bearer-token-here" curl -k -X POST "https://$HOST:8443/v1/message" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "message": { "rows": { "items": [ [{"text": "HELLO WORLD"}] ], "vertical_alignment": "center" } } }'
Python Client

A full Python client with certificate pinning, multi-board support, and a CLI is available on GitHub. Here's the minimal pattern:

python
import requests HOST = "192.168.1.42" TOKEN = "your-bearer-token-here" BASE = f"https://{HOST}:8443/v1" AUTH = {"Authorization": f"Bearer {TOKEN}"} def send_message(rows, vertical_alignment="top", transition=None): payload = { "message": { "rows": { "items": [[{"text": r}] for r in rows], "vertical_alignment": vertical_alignment, } } } if transition: payload["transition_animation"] = transition r = requests.post(f"{BASE}/message", json=payload, headers=AUTH, verify=False) r.raise_for_status() return r.json() # Show a centered two-line message with animation send_message(["GATE 12", "NOW BOARDING"], vertical_alignment="center", transition="staggered_start")
Home Assistant
GeneralFuturics/ha-flappyboard — native Home Assistant integration
A custom integration that adds Flappy Board as a proper HA device with three entities: a connectivity binary sensor, a clear button, and a notify entity for sending messages from automations. Installs via HACS or by copying custom_components/flappyboard/ into your config directory. Supports certificate pinning and the full flappyboard.send_message service with transition animations and layout options.

Alternatively, a lightweight REST command in configuration.yaml works without installing the custom integration:

yaml — configuration.yaml
rest_command: flappyboard_message: url: "https://192.168.1.42:8443/v1/message" method: POST verify_ssl: false headers: Authorization: "Bearer YOUR_TOKEN" Content-Type: "application/json" payload: > { "message": { "rows": { "items": [[{"text": "{{ line1 }}"}], [{"text": "{{ line2 }}"}]] } } }

Call it from an automation or script:

yaml — automation action
action: rest_command.flappyboard_message data: line1: "DOORBELL" line2: "FRONT DOOR"
Save & Restore Configuration
shell
# Save current config curl -k -H "Authorization: Bearer $TOKEN" \ "https://$HOST:8443/v1/config" > backup.json # Restore it later curl -k -X PUT "https://$HOST:8443/v1/config" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @backup.json