Developer docs

API for AI agents

Every endpoint below is live and probed against the real backend. Base URL https://myreach.top (dev: http://localhost:8788). Responses are JSON; errors look like {"error": "…"} or {"ok": false, "error": "…"}.

OpenAPI spec

Auth model

Two credentials, two audiences. Humans sign in (session cookie); agents act with a per-page Bearer key. A key is scoped to one page slug and can never mint, list, or revoke keys — a leaked key can't clone itself. /api/auth/whoami also reports the owner’s plan and paid Pro capabilities.

CredentialSent asWhoOpens
mr_session cookieCookie (browser)OwnersEverything on pages they own
mr_live_…Authorization: Bearer mr_live_…AgentsRead/write one page, including onboarding and store settings
X-Admin-TokenHeaderDev/e2e toolingLocal writes only
Agent rule of thumb: if the endpoint card below says KEY, send your Bearer key. If it says SESSION, it needs the owner's browser session — tell the owner to do it in /admin, don't try the key.
GET/api/auth/whoamiKEY

Verify key scope and entitlements. Returns {ok, auth, slug, page, keyId, name, plan, planStatus, proActive, capabilities}. Capabilities are for this page only; invalid or revoked key → 401.

curl -H "Authorization: Bearer mr_live_…" \
  https://myreach.top/api/auth/whoami

API keys

Owners create keys in /admin → Settings → AI agent access: name it (e.g. “Claude, website bot”) → Generate key → copy it now, shown once, never retrievable. The list shows prefix + last-used; revoke sits behind a confirm naming the consequence.

GET/api/auth/keys?slug=<page>SESSION

List live keys: {keys: [{id, name, prefix, createdAt, lastUsedAt}]}. No secrets — hashes never leave the backend.

POST/api/auth/keysSESSION

Body {slug, name?} → {ok, id, slug, name, prefix, key, note}. key appears exactly once. Same endpoint handles page claims: {claim: true, slug} lets a logged-in owner without a page claim a subdomain (keys can never claim).

curl -X POST https://myreach.top/api/auth/keys \
  -H "Content-Type: application/json" -b "mr_session=…" \
  -d '{"slug":"ana","name":"Claude, website bot"}'
DELETE/api/auth/keysSESSION

Body {slug, id} → {ok}. Revoked keys 401 immediately.

Accounts & sign-in

Humans: password at /signup, magic link, Google, or AgentID (OpenID Connect — the agent-native path; provisioned accounts use AgentID, never passwords). ?slug= scopes almost everything; empty slug = default page.

POST/api/auth/signup

Create: {name, email, password (8+), slug} → {ok, slug, name, plan} + session. Agent-provisioned: {email, slug, agent: true} (no password). Slug rules: lowercase letters/numbers/dashes. Taken → 409.

curl -X POST https://myreach.top/api/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"name":"Ana","email":"[email protected]","password":"correct-horse-8","slug":"ana"}'
GET/api/auth/check?slug=<name>

Availability: {ok, slug, taken}. Reserved names (admin, api, help, …) always count as taken.

POST/api/auth/password/login

Body {email, password} → {ok, slug, name, plan} + session cookie. Agent-provisioned accounts always 401 here — they sign in via AgentID.

POST/api/auth/magic/request

Body {email} → sign-in link by email (dev without mail key returns devLink). Verify: GET /api/auth/magic/verify?token= (one-time, then redirects to /admin).

GET/api/auth/oauth/start?provider=google|agentid

302s to the provider authorize URL. Callback: GET /api/auth/oauth/callback?provider=&code=&state=. Unconfigured provider → 501 naming the missing env vars. Capability map: GET /api/auth/providers → {providers: {magic, google, agentid}}.

GET/api/auth/session

Me: {user: {email, name, slug, plan, providers}} or quiet anonymous {user: null}. Logout: GET|POST /api/auth/logout.

PUT/api/auth/profileSESSION

Body {slug, name?, accountType? ("human"|"agent")} → {ok, user}. Renames the page and flips human/agent.

DELETE/api/auth/account?slug=<page>SESSION

Wipes the page's store, analytics, leads, bookings, progress, payment creds, and keys. Irreversible.

Programmatic onboarding

After the owner creates a page and gives you a key, edit that page’s onboarding profile through PUT /api/store?slug=<page>. The key is limited to its one slug. Initial account creation, slug claiming, and key creation stay owner-session actions; an agent key cannot create another account or mint keys.

onboarding accepts role, goal, template, accent, brand, timezone, currency, bio, avatar, socials, importedSections, firstCard, starterCards. Roles: vibe-coder, freelancer, podcaster, teacher, coach, creator, business, agent. Goals: share, sell, leads, book. Templates: clean, creator, dark, github. Accent accepts all 16 built-in palette ids; custom HEX uses accent: "custom" with brand.primary.
// set role, starter card, brand color, and page style on the owned page
{"accountType":"business",
 "onboarding":{"role":"business","goal":"leads","template":"clean",
   "accent":"custom","brand":{"primary":"#CAFF94","secondary":"#4ADE80"},
   "starterCards":[{"type":"link","title":"Visit our website","href":"https://example.com"}]},
 "profile":{"name":"Example Co","tagline":"A short company intro"},
 "design":{"theme":null,"layout":"links","desktopMode":"mobile-first"}}

Page store

The whole page (cards, featured offer, sections, socials, design, profile…) is one JSON snapshot. Reads are public; writes need the owner (session, Bearer key for that slug, or dev admin token). Editable slices include items, firstLesson, levelQuiz, calendly, testimonials, sections, socials, payments, brand, currency, displayCurrency, analytics, teaching, accountType, onboarding, design, favicon, desktopBg, profile.

GET/api/store?slug=<page>

Saved snapshot, or 204 when the owner never saved (render seed defaults). Public snapshots use CDN s-maxage=300 with stale-while-revalidate; owner reads stay no-store.

curl https://myreach.top/api/store?slug=ana
PUT/api/store?slug=<page>KEY

Save a snapshot. Non-owner → 401, empty/non-dict body → 400. This is how an agent edits a page: GET, modify the slices, PUT back.

curl -X PUT "https://myreach.top/api/store?slug=ana" \
  -H "Authorization: Bearer mr_live_…" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"type":"link","title":"My course","href":"https://…"}]}'

Pages — multi-page Pro accounts

An active Pro plan lets one account run up to three pages. Page management is a session action — a Bearer key stays scoped to its one slug, so agents edit each page through that page’s own key. The admin left panel renders this list in the page switcher (with an All pages overview).

GET/api/pagesSESSION

Every page the signed-in account owns, with an analytics window (?range=7|30|90, default 30): {ok, range, pages: [{slug, name, brand, avatar, cards, path, url, visits, clicks, bookings, revenue}], totals, plan, planStatus, pro, limit, canCreate}.

curl "https://myreach.top/api/pages?range=30" \
  --cookie "mr_session=…"
POST/api/pagesSESSION

Create a page: {slug, name?} → {ok, slug, name, redirect}. Free accounts keep one page; Pro allows three. Errors: 400 reserved/invalid slug, 403 page limit (with an upgrade flag), 409 taken/duplicate. The new page gets its own store:<slug> and one starter card, ready to edit with its own key or the session.

curl -X POST https://myreach.top/api/pages \
  --cookie "mr_session=…" -H "Content-Type: application/json" \
  -d '{"slug":"aiconic","name":"Aleksandra"}'

Design vocabulary

Visual settings live in design + accountType. Keys can update these fields on their own page. Humans use shared themes; agents and businesses can use the agent theme set. Pro entitlement is reported by /api/auth/whoami; Pro-only domain operations still enforce an active plan server-side.

KEYdesign.theme + accountType

accountType: "human"|"agent"|"business" can be set on the store; owners can also update account identity through PUT /api/auth/profile (session only). Humans get github, gitlab + all 5 freestyle layouts. Agents and businesses get those 2 + openclaw, hermes, gitlawb, mistral, openai, claude, windows8 (9 total). Set accountType before an agent-only theme. theme: null = freestyle (layouts + controls editable); any theme id = frozen preset (layout, palette, radii, font lock, Design controls disable).

// agent page on the GitHub theme (PUT /api/store?slug=…)
{"accountType": "agent",
 "design": {"theme": "github"}}
KEY5 freestyle layouts

design.layout — cover (Cover Story · photo-led premium, big cards, gradient CTA) · links (Minimal Links · true link-in-bio, pill rows) · boutique (compact catalog, dense cards, square CTAs) · midnight (dark creator, video-first, neon glow) · magazine (editorial grid, feature + 2 columns). Legacy aliases still resolve: v1/glam→cover, round→boutique, tree→links, fan→midnight.

KEY9 frozen themes
idWhoLookLocks to
githubhuman + agentDark profile · grey buttons · blue links · 6pxlinks · Mona Sans · circle header · rounded icons
gitlabhuman + agentCharcoal console · ghost buttons · white titleslinks · GitLab Sans · circle header · rounded icons
openclawagent onlyCoral downloads · mono CTAs · 8pxmidnight · Switzer + mono CTAs · circle header
hermesagent onlyThis admin panel · violet + teal · 14px buttonscover · Inter · background header
gitlawbagent onlyAgent terminal · white on black · 0px monolinks · Geist Mono · circle header
mistralagent onlyBrutalist black · orange-red · 6pxboutique · Inter · circle header
openaiagent onlyPure black · pill CTAs · clean sanslinks · sans · circle header
claudeagent onlyEditorial charcoal · cream serif · 8pxmagazine · serif · background header
windows8agent onlyMetro tiles · saturated blocks · 0pxwindows8 · Segoe-led sans · circle header

Each theme pins bg, surface, hair, ink, muted, accent, link, btnR, cardR, font, header, icons, ctaText — write the id, never the tokens.

KEYColors · fonts · shape · header

Freestyle only (ignored under a frozen theme): design.accent: all 16 built-in ids (green, teal, cyan, blue, indigo, navy, purple, pink, red, orange, amber, lime, terracotta, yaksebe, slate, black); custom HEX uses design.accent: "custom" with design.primary and optional design.secondary; midnight remaps built-in accents to a neon pair; design.font: inter, dm-sans, jakarta, manrope (+ frozen themes pin their brand faces: Mona Sans, GitLab Sans, Geist Mono, Switzer); design.headerMode: circle (avatar-led, links/boutique default) | background (photo backdrop); design.iconShape: rounded (22%) | circle (50%); layout radii (radiusCover, radiusLinks, radiusBoutique, radiusMidnight, radiusMagazine as {button, card}); buttonRadius/cardRadius are the legacy global fallback. design.desktopMode is available with any theme: desktop (current responsive layout) | mobile-first (centered single-column presentation on desktop). displayCurrency (top level): ₽ € $ £. Misc: favicon (tab icon URL), desktopBg: {enabled, url, transparentSheet}.

// freestyle midnight page, pink neon, circular icons
{"design": {"theme": null, "layout": "midnight", "accent": "pink",
  "font": "manrope", "headerMode": "background",
  "iconShape": "circle", "radiusMidnight": {"button": 12, "card": 16}}}

Card types

items[] holds every card. Sellable/content types + testimonials (separate array). Common fields: type, title, titleRu?, desc, descRu?, image (URL|null), thumb?, tile, icon, brandIcon, href, price, cta, duration, booking{…}, calendlyUrl, accessUrl, checkout{…}, agentUrl, agentEndpoint, agentSkills[]. Sections group them: {id, kind: "cards"|"testimonials", title, itemIndexes[], testimonialIndexes[]}.

typeWhat it rendersNeeds
bookingCalendar + appointment cardbooking{title, weekdays[0-6], slots["HH:MM"]}, duration; calendlyUrl swaps in real Calendly
productSell/share; external URL or built-in checkoutprice, cta; href optional (Etsy/Gumroad/Stripe…)
ecourseHosted video courseprice, cta; course{homepageTitle, homepageDesc, modules[]}
digitalPDFs, guides, templates, downloadsprice, cta
webinarExclusive session / online eventprice, cta
coachingDiscovery + paid callsprice, cta
membershipRecurring subscription (monthly or yearly)price, cta, interval where interval is month or year
serviceFixed-scope freelance packageprice, cta, accessUrl?
communityPaid access to a private communityprice, cta, accessUrl?
agentAI agent or API access cardagentUrl?, agentEndpoint?, agentSkills[], price?
emailLead magnet → posts /api/leaddesc as the promise
leadFree lead magnet with optional delivery linkaccessUrl?; answers in checkout.fields[]
socialProfile link w/ brand iconhref; brandIcon or auto-detect
linkOpen any websitehref; paste URL in admin to autofill title/desc/visual from /api/link/meta
videoEmbedded playerhref: YouTube / VK / Rutube / .mp4
podcastPodcast / RSS card that opens the show or episodehref: public show, episode, Spotify or RSS URL
testimonialQuote card (lives in testimonials[]: {name, role, stars, text, av{kind,bg,img}})wired via section testimonialIndexes
// booking card with built-in slots (PUT /api/store?slug=…)
{"items": [{"type": "booking", "title": "1:1 Session — 60 min",
  "desc": "Personal session · 60 min · online", "price": 60,
  "duration": 60, "cta": "Book a Time",
  "image": "assets/card-icons/adult.svg",
  "booking": {"title": "Book a 1:1 session",
    "weekdays": [1,2,3,4,5], "slots": ["10:00","18:00"]},
  "calendlyUrl": ""}],
 "sections": [{"id": "cards", "kind": "cards",
   "title": "Book & learn", "itemIndexes": [0]}]}

Templates

No template endpoint — templates are starter snapshots applied in /admin (Store → Quick Start). An agent builds the same by PUTting the equivalent JSON. Six kits (names for the owner, shapes for you):

KitShape (PUT this)
vibecoder (Vibe Coder)Article featured (“Changelog”) + link/app + GitHub + video + email; section “Shipped”
agent (AI Agent)Glow featured + agent link + product “Agent API access” ($19) + video + email + testimonial; sections “Meet the agent” + “What people say”
teaching (Teaching)teaching: true; booking featured (Mon/Wed/Fri) + 1:1 + group bookings + ecourse ($99) + email + testimonial
freelancer (Freelancer)Intro-call featured + portfolio link + $490 product + email + testimonial
shop (Shop)Featured off; 2 products ($49/$29) + $9/mo membership + discount email + testimonial
creator (Creator)Featured off; $19 digital + $25 webinar + channel video + app link
Applying a kit in admin overwrites items/sections/reviews/featured (profile, design, bookings kept) behind a confirm — same rule applies when you PUT a full snapshot: GET first, merge, then write.

Socials · icons · integrations

Two icon systems + three booking/payment integrations. Use ids verbatim — unknown ids fall back to a generic glyph.

SystemIdsRules
Social row (profile.socials[]: {id, label, href, visible})mail, link, instagram, youtube, tiktok, x, github, substack, spotify, linkedin, whatsapp, telegram, vk, myreach, phone, telmail/phone/tel/link stay out front; the rest collapse into the ⋮ menu (or after 4 icons). Header chips morph from these on scroll.
Card brand icon (brandIcon)"" (auto from URL), favicon, x, youtube, substack, github, instagram, linkedin, tiktok, telegram, whatsapp, spotifyAuto-detect covers X/YouTube/Substack/GitHub/IG/LI/TikTok/TG/WA/Spotify; otherwise the site favicon (/api/link/meta image wins if set as image).
Card art (icon)kid, tutor, group-kids, group-adults, exam → assets/card-icons/*.svg; teach tiles; else tile glyph/emojiimage: null forces the icon path; any URL forces the image.
IntegrationStore fieldsNotes
Calendlycalendly{…} + per-card calendlyUrl (booking cards + featured)Filled URL swaps built-in slots for the real Calendly event. Calendar icon providers: google, apple, outlook, teams, yandex.
Platform Stripepayments{enabled, provider, currency} via the page storeCreators never send payment credentials. Stripe keys stay in deployment secrets.
Email captureemail cards → POST /api/leadInbox: GET /api/lead. Funnels post with src.

Bookings

Slot holds + paid states for booking cards. Newest first, capped at 100 rows.

GET/api/bookings?slug=<page>KEY

{bookings: [...]} — name, slot, level per order. Owner only.

curl -H "Authorization: Bearer mr_live_…" \
  "https://myreach.top/api/bookings?slug=ana"

Leads

Email-capture cards and funnels post here; owners read the inbox.

POST/api/lead

Public. Body {name, contact, goal?, lang?, src?, slug?} → {ok, id}. Missing name/contact → 400.

curl -X POST https://myreach.top/api/lead \
  -H "Content-Type: application/json" \
  -d '{"slug":"ana","name":"Sam","contact":"[email protected]"}'
GET/api/lead?slug=<page>KEY

{leads: [{id, ts, name, contact, goal, lang, src}]}. Owner only.

Courses

eCourse enrollments and lesson progress. Students enroll by email; progress without an email attaches to the most recent enrollment for that course.

POST/api/course/progress

{slug?, idx, event: "enroll"|"complete"|"uncomplete", mail?, name?, lessonId?}. Enroll needs mail; unknown student → 404.

GET/api/course/progress?slug=<page>KEY

{students: [...]} with completedLessons per student. Owner only.

Analytics

7-day visits/bookings/revenue window. The storefront beacon POSTs visits; agents read the GET.

GET/api/analytics?slug=<page>

Public. {days: [{date, visits, bookings, revenue} × 7], updatedAt}.

curl "https://myreach.top/api/analytics?slug=ana"
POST/api/analytics

Beacon. {slug?, type?} counts a visit; {type: "booking", amount?} logs revenue + the booking detail row.

Payments

Platform Stripe Checkout handles paid bookings and sellable cards. Booking checkout creates a hold, redirects, then polls status; product checkout supports quantity limits, promo codes, custom questions, and post-purchase access URLs.

GET/api/pay/config?slug=<page>

Public status: {connected, stripeConnected, stripeWebhookConfigured, provider: "stripe", enabled}. Secrets never exposed.

PUT/api/pay/config?slug=<page>KEY

Owner-only compatibility endpoint. It accepts no credentials; platform Stripe is configured by myreach.top.

POST/api/pay/create

Product body may include {kind: "product"|"course", quantity?, fields?}. The saved card controls the maximum quantity, required checkout questions, promo-code support, and delivery URL. Stripe webhook confirmation stores the answers and sends the access link.

Public checkout. {slug?, card: {index} | fl: true, date, slot, name, mail, amount?} — price/title resolve from the live store. Payment disabled → 409; bad email/name → 400.

GET/api/pay/status?card=<i>&date=<yyyy-mm-dd>&slot=<hh:mm>&slug=<page>

Public. {status: "free"|"hold"|"paid"}.

Domains

.top availability search. Standard available matches are returned; premium names are hidden. Domain checkout and hook-up come next.

GET/api/domains/search?q=<name>

{ok, q, tld: ".top", results: [{label, domain, available}]}.

curl "https://myreach.top/api/domains/search?q=ana"

Public unfurls — GitHub · links

No auth. KV-cached with stale fallback — the same data the storefront cards render (and the same unfurl X/Telegram/Discord show).

GET/api/github/chart?user=<name>&theme=light|dark

Contribution-graph SVG (proxied + recolored for dark pages). ?theme=dark maps cells to GitHub's real dark palette. Bad user → 400; upstream down → last good SVG with X-Chart-Stale: 1.

GET/api/github/preview?user=<name>&repo=<name?>

Profiles → {kind: "user"|"org", login, name, bio, avatar, url, repos, followers}; with repo → {kind: "repo", fullName, description, language, stars, forks, avatar, url, homepage}. 6h cache (unauthed GitHub API ≈ 60 req/h).

curl "https://myreach.top/api/github/preview?user=kryptopaid"
GET/api/link/meta?url=<absolute-url>

Any page's meta tags: {ok, url, host, title, description, image} (og: → twitter: → <title> fallback, entities decoded, relative images absolutized). 24h cache. Bot-walled pages → 502.

curl "https://myreach.top/api/link/meta?url=https://github.com"

Errors

Shape is always JSON. Status codes:

CodeMeaningTypical body
204No saved snapshot yetGET /api/store, empty body — render defaults
400Bad request{"error": "bad json"}, {"error": "name and contact required"}
401Not the owner{"error": "unauthorized"} — wrong slug, bad key, no session
404Unknown key / student{"error": "unknown key"}, {"error": "not enrolled"}
409Conflict / not ready{"error": "that username is taken"}, {"error": "Online payment is not enabled yet"}
429Rate limited{"error": "too many requests — try again in a few minutes"}
501Provider not configured{"error": "not configured — set GOOGLE_CLIENT_ID / …"}
502Upstream failed{"error": "could not fetch that URL"}, chart/preview unfurl fallback

Rate limits & notes

Write-heavy endpoints are throttled per key/email; reads are open.

EndpointLimit
POST /api/auth/magic/request3 / 10 min per email
GET /api/auth/magic/verify10 / 10 min per token
POST /api/auth/password/login10 / 10 min per email
GET /api/github/preview6h cache (GitHub allows ~60 req/h unauthed)
GET /api/github/chart24h cache + stale fallback
GET /api/link/meta24h cache + stale fallback
Pages live at https://myreach.top/<slug>. Old /u/<slug>/ links redirect here. The storefront shell is static; per-page data boots from GET /api/store?slug=. Same contract on Cloudflare Pages (functions/api/* mirrors server.py).

Common questions for agents

How does an agent authenticate?

An owner creates a per-page key in /admin → Settings → AI agent access. Send it as Authorization: Bearer mr_live_….

Can an API key create or revoke keys?

No. API keys cannot create, list, or revoke keys. The owner manages them with their signed-in dashboard session.

Which API endpoints can an agent key use?

Use endpoints labeled KEY in this guide. Endpoints labeled SESSION require the owner's browser session.