Install
openclaw skills install @bgaut007/billboardbug-deal-creatorPut a real billboard on the real map. Create GPS-anchored BillboardBug deals people see in AR on iOS, Android, web and Meta Quest, chirp them, and verify with read-back. Use when asked to post a billboard, deal, promotion, or chirp on BillboardBug.
openclaw skills install @bgaut007/billboardbug-deal-creatorBillboardBug puts a business's deal on a real map location as a billboard. Real people see it in the BillboardBug app (phone, web, and Meta Quest), walk up to it and interact with it, and chirp it (share it as a social post). One API call puts a real, visible billboard in the world, and you get IDs you can read back as proof. It's governed by design: you confirm the business, never invent offers, and leave a receipt with read-back proof for every action.
https://east-us-api.billboardbug.comHeaders on every call: X-Api-Key: $BILLBOARDBUG_API_KEY and X-Version: 1
(X-Version: 3 on /blobs).
BILLBOARDBUG_API_KEY.Key safety (non-negotiable):
BILLBOARDBUG_API_KEY environment variable. Never paste it into chat,
posts, files, logs, or commit it.isEnabled: false) when it ends.Check what you already have, then ask for all missing items in a single message (don't drip questions one at a time):
| Field | Notes |
|---|---|
| Business name | exact name, confirmed |
| Street address | goes in locationDescription |
| Latitude / longitude | of the business, from the address; never guess |
| Deal title | max 18 characters |
| Description | max 1000 characters; only what the business said |
| Hours | 24/7, or per day: open/close times or closed |
| End date | for the receipt (see rule 4) |
| Image or video | or permission to generate one |
Title too long? Never silently truncate. Propose one or two shorter titles (≤ 18 chars) and let the human pick.
All seven <day>Enabled flags are required (monday … sunday).
| Human says | Send for that day |
|---|---|
24/7 | every day: <day>Enabled: true, <day>24Hours: true |
| open all day | <day>Enabled: true, <day>24Hours: true |
9am-5pm | <day>Enabled: true, <day>24Hours: false, <day>Open: "09:00", <day>Close: "17:00" |
| closed | <day>Enabled: false (send <day>24Hours: false) |
<day>Open / <day>Close are HH:MM 24-hour strings (pattern ^\d{2}:\d{2}$), used only when
<day>24Hours is false.
The spec schema marks imageId/videoId optional, but the live API rejects a deal with neither
(400, "Image or video required"). If the human gives no media, generate an image (ask first if your
human wants approval for artwork). Prefer realistic photos.
Generate (optional):
curl -s https://east-us-api.billboardbug.com/ai/image \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 1" -H "Content-Type: application/json" \
-d '{"prompt":"Realistic photo of a cozy neighborhood coffee shop storefront, morning light","aiProvider":5,"width":640,"height":373}' \
> ai.json
# Response is an array; base64Data is a data URI: strip the "data:image/jpeg;base64," prefix.
python3 -c "import json,base64;d=json.load(open('ai.json'));d=d[0] if isinstance(d,list) else d;open('billboard.jpg','wb').write(base64.b64decode(d['base64Data'].split(',',1)[1]))"
aiProvider: 5 = Grok (recommended; GET /global-config/all lists providers by type number).
The schema names the enum "Grok"; the numeric 5 is what the docs' example uses and what has worked live.python3 -c "from PIL import ImageOps,Image;ImageOps.fit(Image.open('billboard.jpg'),(640,373)).save('billboard.jpg',quality=90)"Upload (note X-Version: 3, multipart field file):
curl -s "https://east-us-api.billboardbug.com/blobs?category=billboard-image" \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 3" \
-F "file=@billboard.jpg"
# -> {"id":"<blobId>.jpg"} pass it as imageId
Categories (from the spec):
billboard-image → imageId, billboard-video → videoId (640x373 recommended).post-image → imageId, post-video → videoId.
A deal video uses billboard-video, not post-video.Before creating, list the account's deals and look for the same business + address + title:
curl -s "https://east-us-api.billboardbug.com/business/quick-get-deals?limit=100&offset=0" \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 1"
# array of deals: storefrontName, locationDescription, title, isEnabled, storefrontId, billboardId...
# page with offset=100, 200, ... until fewer than 100 come back
If a matching deal exists, tell the human and offer to update it
(PUT /business/quick-update-deal/{storefrontId}/{billboardId}) instead of creating a second one.
Show the human a summary and wait for an explicit yes before you create:
About to create on BillboardBug (account: <owner>):
Business: <exact name>, <address>
Location: <lat>, <lng>
Title: <title> (<n>/18)
Text: <description>
Hours: <Mon–Sun summary>
Media: <imageId / videoId>
Category: <name> (<categoryId>)
End date: <date> (receipt only)
Featured: no
Create it? (yes/no)
Then create:
curl -s https://east-us-api.billboardbug.com/business/quick-create-deal \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 1" -H "Content-Type: application/json" \
-d '{
"title": "Fresh Coffee Deal",
"description": "Text exactly as the business provided it.",
"imageId": "<blobId>.jpg",
"name": "Exact Business Name",
"locationDescription": "123 Main St, Anytown, ST",
"locationLat": 0.0,
"locationLng": 0.0,
"categoryId": "ce8f8f8f-8f8f-8f8f-8f8f-8f8f8f8f8f8f",
"isEnabled": true,
"isFeatured": false,
"mondayEnabled": true, "monday24Hours": false, "mondayOpen": "07:00", "mondayClose": "17:00",
"tuesdayEnabled": true, "tuesday24Hours": false, "tuesdayOpen": "07:00", "tuesdayClose": "17:00",
"wednesdayEnabled": true, "wednesday24Hours": false, "wednesdayOpen": "07:00", "wednesdayClose": "17:00",
"thursdayEnabled": true, "thursday24Hours": false, "thursdayOpen": "07:00", "thursdayClose": "17:00",
"fridayEnabled": true, "friday24Hours": false, "fridayOpen": "07:00", "fridayClose": "17:00",
"saturdayEnabled": true, "saturday24Hours": true,
"sundayEnabled": false, "sunday24Hours": false
}'
# -> contains storefrontId and billboardId. Save both.
Replace the 0.0 placeholders with the business's real coordinates. Use only hours the business gave you.
Optional fields:
categoryId: storefront category. ce8f8f8f-8f8f-8f8f-8f8f-8f8f8f8f8f8f is Miscellaneous, a safe
default. For a better fit, list categories (about 7,000, key required) with
GET /business/storefront-categories and pass the matching id.altitudeMeters (0–1000): meters above ground level. Omit for a standard pole mount.isFeatured / unfeatureAt (ISO date-time): featured placement is paid (daily rate in
GET /global-config/all → billboardDayAdCost, dollars × 100, and the account needs a billing
method). Never set isFeatured: true without your operator's explicit approval; default false.Update later with the same body: PUT /business/quick-update-deal/{storefrontId}/{billboardId}.
Search deals: GET /business/deals/search?search=<3+ chars>&limit=25&offset=0 (key required; the
spec's only params are search, limit, offset).
# optional image for the chirp
curl -s "https://east-us-api.billboardbug.com/blobs?category=post-image" \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 3" -F "file=@billboard.jpg"
curl -s https://east-us-api.billboardbug.com/posts \
-H "X-Api-Key: $BILLBOARDBUG_API_KEY" -H "X-Version: 1" -H "Content-Type: application/json" \
-d '{"text":"New billboard up at Exact Business Name. Walk up to it in the BillboardBug app! #billboardbug","imageId":"<postBlobId>.jpg","isActive":true,"isMature":false}'
# -> {"id":"<chirpId>", ...}
Edit: PUT /posts/{id}. Delete: DELETE /posts/{id}. Comment: include parentPostId.
Chirp fields (PostRequest): isActive (required), text, imageId, videoId, videoUrl,
parentPostId, isMature. Supply at least one of text/imageId/videoId. Read back with
GET /posts/{id} (no key needed); it only counts once that returns it with "isActive":true.
The post schema has no billboard/deal ID field: to chirp a billboard, name the exact business,
attach the billboard image (re-uploaded as post-image), and record the billboardId in your receipt.
Live example: https://east-us-api.billboardbug.com/posts/01a143a0-6b07-75c3-ae5e-c6af4a0ee7d6
Read back (deal read-back works without a key):
curl -s -H "X-Version: 1" https://east-us-api.billboardbug.com/business/deal/<storefrontId>/<billboardId>
curl -s -H "X-Version: 1" https://east-us-api.billboardbug.com/posts/<chirpId> # no key needed
Confirm isEnabled is true and the image/video is attached. Then report one receipt block:
RECEIPT
start: <ISO time> | request: <what the human asked>
agent: <your name>, operated by <human>, account <BillboardBug account owner>
business: <exact name>, <address>
coordinate: <lat>, <lng> (omit in public posts if your human prefers)
window/end: <time window> / <end date>
deal: storefrontId=<..> billboardId=<..> readback: OK|FAILED
chirp: id=<..> readback: OK|FAILED
feed entry: <link or id of the one post you made naming the deal ID>
gaps/errors: <anything that failed or the API could not do>
<day>Enabled, bad HH:MM, no image/video, wrong blob category), and retry once. If it fails
again, stop and report the error.X-Version: 3 for /blobs, 1 elsewhere). Tell the human; do not retry and never ask for the key in chat.data:...;base64, prefix.