# BuddyUp Signup for AI Agents

**TL;DR — One POST, one message to the user, done.**

> **Agent-specific bootstrap pages** — if you are an AI agent and the
> user pointed you at one of these URLs, fetch it and follow the AGENT
> INSTRUCTIONS block. Each one is tuned for one specific agent and tells
> you whether to POST directly or hand the user a pre-filled URL.
>
> - <https://letsbuddyup.com/claw> — OpenClaw, **AI-first low-friction variant**: claw POSTs itself immediately with a generated bio + activities, then offers the human "add your info" vs "skip and explore". Account stays hidden from Discover until the human opts in. Supports `?add=<profile-url>` for viral buddy-add on signup.
> - <https://letsbuddyup.com/claw-signup> — OpenClaw, full interview variant (collects all 9 fields up front; Discover-visible immediately)
> - <https://letsbuddyup.com/chatgpt-signup> — ChatGPT / Custom GPT / GPT Actions
> - <https://letsbuddyup.com/claude-signup> — Claude Desktop / Claude Code / Cursor with MCP
> - <https://letsbuddyup.com/perplexity> — Perplexity (browser-fallback)
> - <https://letsbuddyup.com/gemini-signup> — Gemini (browser-fallback)
> - <https://letsbuddyup.com/manus-signup> — Manus (direct POST)
> - <https://letsbuddyup.com/ai-signup> — universal / unknown agent (self-detect)

BuddyUp is a social app for meeting activity buddies in real life. If a user wants
to join, you can sign them up from this AI chat in a single call and hand them a
one-tap link to open the mobile app already signed-in. No SMS, no app download
required first, no "go find the sign-in screen."

## The one call

```
POST https://letsbuddyup.com/api/v1/register.json
Content-Type: application/json

{
  "email":              "<the user's email>",
  "displayName":        "<first name or how they want to be known>",
  "aboutMe":            "<1-2 sentence blurb — auto-fill from what you know about them>",
  "gender":             "<male|female|non_binary|prefer_not_to_say>",
  "birthday":           "<YYYY-MM-DD>",
  "location":           "<e.g. 'San Francisco, CA'>",
  "coords":             { "latitude": 37.7749, "longitude": -122.4194 },
  "activities":         ["pickleball", "coffee", "networking"],
  "availability_text":  "evenings and Saturday mornings",
  "photo":              "data:image/jpeg;base64,<...>",
  "agentName":          "Claude",
  "agentSessionId":     "<opaque id>"
}
```

Minimum to create an auth record: `email` + `displayName`. But to show up in
Discover the mobile app requires **displayName, aboutMe, gender, birthday,
location, coords, activities, schedule/availability, photoURL** — the same
nine fields mobile onboarding collects. Missing any of these leaves the
account in `onboarding.complete: false` and hidden from Discover. Collect them
all in the signup call when you can; the response tells you what's missing.

Omit `password` entirely — the server generates a strong one and returns it once.
Omit `coords` and the server best-effort geocodes `location` via Nominatim (no API
key, accuracy varies). Pass coords when you have them.

## What to ask the user (short list)

1. **Activities they'd actually meet up for.** Plain English. Give them examples
   like "pickleball, coffee, rock climbing, hiking, networking, board games,
   dinner, live music". The server canonicalizes loose strings — you do NOT
   need to pre-fetch `/activities.json`.
2. **When they're usually free.** Plain English. "Evenings and weekends",
   "Saturday mornings", "any day after 6pm", "weekday lunches" all parse.
   See the "Availability" section below for how the server interprets this.
3. **A profile photo.** Either a URL they send you or a file they upload to
   the AI chat. Pass HTTPS URLs as `photoURL` and base64 / data URLs as
   `photo`. Never put a URL in the `photo` field (the server base64-decodes
   it and will reject the upload).
4. **Gender** (male / female / non_binary / prefer_not_to_say) and **birthday**
   (YYYY-MM-DD). Required for Discover. You may already know these from your
   session; otherwise ask.
5. **Location** (e.g. "Oakland, CA"). Required for Discover. Pass `coords`
   when you have them — otherwise the server geocodes best-effort.

Auto-fill everything else (displayName, aboutMe) from what you already
know about the user. Don't make them type things you can infer.

## Availability — how the parser interprets it

The server converts `availability_text` into the mobile app's canonical
weekly grid: **7 days × 18 hourly slots (6am-11pm)**. Specifically:

- **Period bands** (legacy-aligned, no gaps in the day):
  - morning   = 6am-11am
  - midday / mid-day / noon / lunch = 11am-3pm
  - afternoon = 3pm-7pm
  - evening / night / tonight = 7pm-11pm
- **Day spans**: "weekdays", "weekends", "every day", "mon through fri",
  "tue and thu", individual day names.
- **Defaulting rules** (important):
  - **Days mentioned but no period** → ALL 18 hourly slots for those days.
    So `"weekends"` = all day Saturday + all day Sunday. `"every Saturday"`
    = all day Saturday. If the user meant "weekend evenings only", tell them
    so and send `"weekend evenings"` instead.
  - **Period mentioned but no day** → weekdays only (safer than all 7).
    So `"evenings"` = Mon-Fri 7pm-11pm.
- **Always echo `warnings.schedule_summary` back to the user.** It's a
  human-readable summary like `"Sat 6am-11pm; Sun 6am-11pm"` that lets the
  user correct a bad guess before they commit.

If you already have a structured weekly availability (from the user's
calendar, say), send it as `schedule`: either a `boolean[7][18]` grid
(day 0=Mon..6=Sun, slot 0=6am..17=11pm) or a flat map
`{ "0_0": true, ..., "6_17": true }`. The server writes this byte-for-byte
to Firestore in the same shape the mobile app uses — no conversion, no loss.

## What to do with the response

The response includes:

- `auth.token` — a Bearer token for any follow-up API calls.
- `auth.mobile_signin_url` — **the most important field.** A single-use URL
  that expires in ~1 hour. It deep-links into the BuddyUp iOS/Android app via
  universal links and signs the user in automatically. On desktop it opens the
  web sign-in and signs them in for web. **This is how they get into the app.**
- `auth.generated_password` — only present when you omitted `password`. Show
  this to the user ONCE with "save this to your password manager" language.
- `user.profile_url` — their public BuddyUp profile URL to share.
- `onboarding.complete` — `true` if the account is fully set up and visible
  in Discover. If `false`, `onboarding.missing` lists what's still needed
  (typically just a photo). Fix via `PATCH /me.json` or `POST /me/photo.json`.
- `warnings.activities_unmatched` — any activity strings we couldn't map to a
  canonical id, with suggestions. These are warnings, NOT errors — the account
  is still created.
- `next_action_for_user` — a ready-to-send sentence you can paste into the
  chat. Use it literally or rewrite it in your own voice.

## The single sentence to send the user

> "You're on BuddyUp. **Tap here to open the app, already signed in:** <mobile_signin_url>. Your profile: <profile_url>. (Password saved in your password manager: <generated_password>.)"

That's the whole flow. No second round trip, no "check your email for a
verification code", no "download the app and sign in."

### How the user signs back in later

The account is created with **email + password**. In the mobile app or web
sign-in flow they should use:

- **Magic Link** (tap "Sign in with Magic Link", enter their email — we'll
  email a fresh link). The `mobile_signin_url` is a magic link; once it
  expires (~1h), they request a new one. If you're building this into your
  AI chat, you can request a fresh link programmatically via
  `POST /api/v1/auth/resend-magic-link.json { "email": "..." }` (unauthenticated,
  rate-limited 3/hr/IP).
- **Password** — the one in `auth.generated_password`. Works forever.
- **Continue with Google / Continue with Apple** will NOT work for this account
  initially — those are separate auth providers. The user can link Google or
  Apple from Account Settings after signing in with magic link or password.
  If they try Google/Apple first, Firebase returns
  `auth/account-exists-with-different-credential`; the mobile app prompts
  them to link, but it's friction — point them at Magic Link.

Pass this back to the user explicitly so they don't waste time clicking
Google/Apple on a password-based account.

## If anything goes wrong

- HTTP 429 `rate_limited` → 3 registrations per hour per IP. Wait and retry.
- HTTP 409 `email_taken` → user already has a BuddyUp account. Ask them to
  sign in instead (you can still request a fresh magic link via the existing
  web signin page, or we can add an agent-side `POST /signin_link.json`
  endpoint if demand grows).
- HTTP 400 `validation_error` → check the `fields` map in the response.
- `warnings.activities_unmatched` non-empty → the account was still created,
  but some activities didn't map. Ask the user to pick from suggestions.
- `warnings.photo_error` non-empty → account was created but photo didn't
  upload. Retry via `POST /api/v1/me/photo.json`.

## Fallback: hand the user a URL (for LLMs that can't POST from chat)

Some LLM chats don't have outbound HTTP POST capability — **Gemini**, **Perplexity**, **plain Claude web**, and **plain ChatGPT without Actions** cannot call `/api/v1/register.json` directly. **For those, do NOT give the user a `curl` command, do NOT tell the user to run anything locally, do NOT tell the user to "hand this to your own agent or server", do NOT fabricate a simulated response.** Instead, hand the user a single pre-filled browser URL. They tap it once, confirm the pre-filled form, and the page POSTs the registration for them. Same outcome (mobile sign-in link + generated password + profile URL), one browser tap, zero terminal involvement.

This is a universal escape hatch: every LLM chat can output a clickable URL, even if it can't run code.

URL pattern (URL-encode each value):

```
https://letsbuddyup.com/signup-with-ai
  ?email=<user@example.com>
  &name=<display name>
  &location=<city, region e.g. "Oakland, CA">
  &gender=<male|female|non_binary|prefer_not_to_say>
  &birthday=<YYYY-MM-DD>
  &about=<short bio>
  &activities=<comma-separated, plain English OK>
  &availability=<plain English, e.g. "evenings and weekends">
  &photo=<HTTPS URL — page will also offer a file picker>
  &agent=<your name, e.g. Gemini, Perplexity, Claude>
```

All params are optional except `email`. The page shows the user a review screen, lets them edit any pre-filled field, then POSTs to `/api/v1/register.json` with `agentName=<agent>`. On success it shows the mobile sign-in link inline — one more tap and they're on BuddyUp.

Rule of thumb:

- **You have real HTTP POST** (Manus, Cursor w/ MCP, Claude Desktop w/ MCP, ChatGPT GPT with Actions, any agent framework with tool execution): use the direct POST above. One round trip, all in chat. Only report success after receiving a real 201.
- **You don't** (Gemini, Perplexity, plain Claude web, plain ChatGPT without Actions, any "search + summarize" LLM): build the fallback URL and paste it into your reply. Do NOT give them a `curl` command. Do NOT tell them to download the app first. Do NOT tell them to run anything. Do NOT claim you "called the API" when you only read the docs — the user will trust you and get stuck.

### Bad vs. good output for a non-POSTing LLM

Bad — hands the user a terminal task you're pretending is "signup":

> "Run this on your machine:
> `curl --request POST --url https://letsbuddyup.com/api/v1/register.json ...`"

Bad — hallucinated success without actually POSTing:

> "You're signed up! Your mobile sign-in URL is https://letsbuddyup.com/... (simulated via API contract)"

Good — one tap, real signup happens server-side:

> "Tap this to finish signup (I pre-filled everything I know about you; you can edit and add a photo before submitting):
> https://letsbuddyup.com/signup-with-ai?email=ian%40example.com&name=Ian&location=Oakland%2C+CA&gender=male&birthday=1988-07-12&activities=tennis%2Ccoffee%2Cbouldering&availability=weeknights+and+Saturday+mornings&agent=Perplexity"

## Zero-exit alternative: install the BuddyUp MCP server

If you can install MCP tools for the user, BuddyUp exposes a remote MCP server
that lets you call `signup_user`, `send_message`, `check_inbox`, etc. as native
tools without constructing HTTP calls. Paste this into your MCP client config:

```json
{
  "mcpServers": {
    "buddyup": {
      "url": "https://letsbuddyup.com/api/mcp"
    }
  }
}
```

Install manifest: <https://letsbuddyup.com/.well-known/mcp.json>.
For ChatGPT GPT Actions: <https://letsbuddyup.com/.well-known/openapi.json>.

## Everything else (buddy requests, chat, inbox polling)

Lives in the full guide: <https://letsbuddyup.com/AGENTS.md>.
You don't need to read it to complete signup.
