Images
How to put a photo on a page when all you have is a URL: the upload route, storage keys, kinds and size caps.
Image fields — avatar, theme.backgroundImage, a link's image — accept only storage keys, never URLs. A key is what POST /api/upload returns after it has fetched, re-encoded (AVIF, EXIF stripped) and stored the picture. This keeps third-party pixels off pages that promise not to track anyone.
Upload by URL
curl -X POST https://tapmy.link/api/upload \
-H "Authorization: Bearer tapmy_sk_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/photo.jpg", "kind": "avatar" }'{ "key": "avatar/66dc…/3f9c….avif", "bytes": 84120, "width": 1000, "height": 1000, "lqip": "data:image/webp;base64,…" }Then save the key into the page:
curl -X PATCH https://tapmy.link/api/profiles/{id} -H "Authorization: Bearer …" \
-H "Content-Type: application/json" -d '{ "avatar": "avatar/66dc…/3f9c….avif" }'Kinds and caps
| `kind` | Used for | Max source size | Stored at |
|---|---|---|---|
avatar | the profile photo | 8 MB | ≤ 1000 px wide, ≈300 KB |
card | a link block's image | 8 MB | ≤ 1400 px wide, ≈350 KB |
background | theme.backgroundImage | 30 MB | ≤ 1600 px wide, ≈600 KB |
Where the crop is anchored
Templates crop: the photo is round, the background is full-bleed, a card image is a strip. Each image field has a companion anchor — avatarFocus, theme.backgroundFocus, a link's imageFocus — as { "x": 0–100, "y": 0–100 } percent of the picture, the point that stays in view. Absent or null means the centre. A face in the top-right of a landscape photo wants something like { "x": 70, "y": 25 }.
curl -X PATCH https://tapmy.link/api/profiles/{id} -H "Authorization: Bearer …" \
-H "Content-Type: application/json" -d '{ "avatar": "avatar/…avif", "avatarFocus": { "x": 70, "y": 25 } }'At creation, page.avatarFocus applies to the photo fetched from avatarUrl. The MCP tool set_avatar_from_url takes a focus argument.
Multipart
The same route accepts multipart/form-data with fields file and kind — what the editor uses — if you do have the bytes.
Shortcut at creation
POST /api/agent/pages takes avatarUrl; the server does the fetch and the save for you. A failure there is reported in warnings and does not stop the page.
Errors
bad_url, blocked (private address, or the host refused us), unreachable, too_large, not_image, processing_failed. HEIC from an iPhone is accepted. Instagram CDN URLs expire within days — copy them at once, which is what avatarUrl does.