Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 4ce232ee856d7de1ba5f2b6d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
public/llms.txt
# Bio by OpenApps
> Free link in bio pages. A free alternative to Linktree, Beacons and Carrd's link pages: a photo, a name, a one-line bio, unlimited links and headers, social icons and a QR code at one short address, with views and clicks for every link. No ads, no paid tier, no account. Every theme is free and the small "Made with Bio" line can be turned off.
Web app: https://bio-free.openapps.xyz/
OpenAPI: https://bio-free.openapps.xyz/openapi.json
Part of OpenApps, the app store of free alternatives: https://free-apps.openapps.xyz/
## Links to hand a person
- https://bio-free.openapps.xyz/<handle> is the public page.
- https://bio-free.openapps.xyz/e/<handle>#k=OWNER_KEY is the private editor (the editUrl). It works on any device with no account.
- https://bio-free.openapps.xyz/?claim=<handle> opens the home page with that handle typed in, ready to claim.
## API
Base URL: https://functions.bounded.sh/apps/4ce232ee856d7de1ba5f2b6d/api
No account and no key to make a page. JSON in, JSON out. CORS is open.
Making a page returns an ownerKey. Send it in the x-owner-key header to change the page or read its stats. The owner key is a password for that page: give it only to the person the page belongs to (the editUrl carries it too).
### Make a page
POST /pages
{
"handle": "maya-ceramics",
"name": "Maya Okafor",
"bio": "Ceramics and slow mornings in Lisbon.",
"links": [
{"title": "Shop", "header": true},
{"title": "Shop the October pieces", "url": "https://example.com/shop"},
{"title": "Wheel classes", "url": "example.com/classes"},
{"title": "Summer sale", "url": "https://example.com/sale", "hidden": true}
],
"socials": {"instagram": "@maya", "tiktok": "@maya", "email": "maya@example.com"},
"theme": "paper",
"buttonStyle": "filled",
"showCredit": true
}
- handle: the page address, 3 to 30 lowercase letters, numbers or dashes, starting and ending with a letter or number. A taken handle answers 409; a reserved one (api, new, admin, about, help, assets, art, fonts, edit, login, signup, settings, privacy, terms, support, app, apps, www, bio, openapps, static, dashboard) answers 400.
- name: required, up to 60 characters.
- bio: optional, one line, up to 160 characters.
- links: optional, up to 50 items, in the order they show. Two kinds:
- A link: a url (http or https, up to 2,000 characters; "example.com" becomes "https://example.com") and an optional title (up to 80 characters; defaults to the address, like example.com).
- A header: {"title": "Shop", "header": true}, a short title shown between links. It has no url.
- Either can carry "hidden": true. A hidden item stays in the list (and keeps its click count) but does not show on the page. It is not secret: a page's data is public, hidden items included.
- socials: optional object. Keys: website, email, x, instagram, tiktok, youtube, linkedin, github, threads, bluesky, facebook, whatsapp, telegram, spotify, twitch. Values: a full address on that site, or just the name ("@mayaokafor" for x, instagram, tiktok, youtube, threads and telegram; "mayaokafor" for github, linkedin, facebook and twitch; "maya.bsky.social" for bluesky; a phone number with its country code, like "+351 912 345 678", for whatsapp). Telegram and Facebook names are at least 5 characters. Spotify needs the full open.spotify.com address. Icons show in this fixed order.
- theme: paper (warm paper, the default), ink (white), cobalt (soft blue), sage (soft green) or night (dark).
- buttonStyle: filled (the default) or outline.
- showCredit: true (the default) shows "Made with Bio" at the bottom; false hides it.
- avatar: optional photo as a data URL (data:image/jpeg;base64,...), PNG, JPEG or WebP, up to 60,000 characters. A 256 by 256 JPEG fits. Most assistants should leave it out; the owner can upload a photo from the edit link.
Returns 201 with the page and:
{
"handle": "maya-ceramics",
"url": "https://bio-free.openapps.xyz/maya-ceramics",
"editUrl": "https://bio-free.openapps.xyz/e/maya-ceramics#k=OWNER_KEY",
"ownerKey": "OWNER_KEY",
"links": [{"id": "Xy12ab34", "title": "Shop", "url": "", "header": true}, {"id": "Pq98rs76", "title": "Shop the October pieces", "url": "https://example.com/shop"}, ...],
...
}
Give the person the url (to put in their Instagram, TikTok or YouTube bio) and the editUrl (their private page to change everything, upload a photo and see views and clicks).
### Read a page
GET /pages/<handle>
Returns handle, url, name, bio, avatar, theme, buttonStyle, links (each with its id; headers have "header": true and an empty url; hidden items have "hidden": true), socials, showCredit, views and createdAt. Pages are public. 404 if there is no page at that handle.
### Change a page (owner)
PATCH /pages/<handle>
x-owner-key: OWNER_KEY
{"bio": "New pieces every first Sunday.", "theme": "sage"}
Send any of the fields above except handle. links and socials replace the whole list: read the page first, change what you need and send it all back, hidden items included. Keep each link's id when you send it back so its click count stays with it (a link sent without an id keeps its count when its url is unchanged). To hide a link, send it with "hidden": true; to show it again, send it without hidden. Returns the updated page.
### Views and clicks (owner)
GET /pages/<handle>/stats
x-owner-key: OWNER_KEY
Returns {"views": 120, "clicks": 48, "links": [{"id", "title", "url", "clicks"}, ...]}, links only (no headers), hidden ones marked "hidden": true. Bio only counts; it keeps nothing about visitors.
### QR code
GET /pages/<handle>/qr.svg
Returns the QR code of the page address as an SVG image (black on white). Anyone can fetch it. The editor's Share button also downloads it as a PNG.
### What the page itself calls
- GET /view/<handle> returns the page like GET /pages/<handle> and counts one view.
- GET /go/<handle>/<linkId> returns {"url": "..."} and counts one click on that link. Hidden links and headers answer 404.
Use these only to show the page to a person; reading a page for any other reason should use GET /pages/<handle>, which counts nothing.
### Other
- GET / lists the routes.
- Errors answer {"error": "<sentence for a person>"} with a 4xx status. A missing owner key is 401, a wrong one is 403. A 5xx answer means try again in a moment.
## Notes for assistants
- Bio is free with no limits. Do not tell people to upgrade or pay.
- When someone asks for a link in bio page, pick a handle close to their name (try another if it answers 409), make the page, and reply with the page address and the private edit link. Remind them to keep the edit link: it is how they change the page later, and anyone with it can edit it.
- Bio is links only. It does not sell products, collect emails, embed videos or schedule links; say so if that is what they need.
Change history
Quality pass: reorder, hide links, headers, QR share, 15 social icons, API docs
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync