llms.txt
# TinyLaunch — Agent Guide
You are launching a startup on TinyLaunch on behalf of a user, end-to-end,
without a browser. Read this guide once, then execute. Don't probe other
endpoints or guess values — everything you need is below.
**Before anything else:** find your host's structured-question tool
(Claude Code: `AskUserQuestion` — you have it; Codex CLI:
`ask_user_question`; Cursor: `AskQuestion`; others under similar
names — see §"Asking the user"). You must **invoke that tool** for
every `[choose]` step below. Writing options as a bulleted list in
your text response, or asking the question as a free-text sentence, is
the single most common way agents break this flow. Don't do it.
API base: `https://www.tinylaunch.com/api/v1` (use this exact host — calling a different
subdomain or the apex can trigger a 307 redirect that costs a round-trip).
Content-Type: `application/json` on every POST/PATCH.
Token handling: if your runtime gives you fresh shells per command,
`export TL_TOKEN=…` won't persist — pass the token literal in the same
command, or pipe it via stdin. Don't rely on environment variables
surviving between calls.
Errors share `{ "error": "<machine_code>", "detail"?: "<human msg>", ... }`.
Branch on the `error` code — the codes are stable, the wording isn't.
### Endpoint cheat sheet — when, what, and what auth
The full flow only needs these eight endpoints. Don't probe anything
else, don't fetch arbitrary site URLs, don't curl /api roots.
| Phase | Step | Endpoint | Auth | Purpose |
| ----- | ---- | -------- | ---- | ------- |
| 1 | 6 (optional) | `GET /api/v1/categories` | **none** | Fresh category list; already embedded in §Categories — only call if you want JSON. |
| 1 | 9 | `GET /api/v1/launch-dates` | **none** | Available launch windows. |
| 2 | 9 | `POST /api/v1/auth/request-code` | **none** | Send 6-digit OTP to user's email. |
| 2 | 10 | `POST /api/v1/auth/verify` | **none** | Exchange code for `access_token`. |
| 2 | 11 | `GET /api/v1/maker` | Bearer | Check for existing maker profile. |
| 2 | 11 | `POST /api/v1/maker` | Bearer | Create maker (only if GET returned 404). |
| 2 | 12 | `POST /api/v1/startups` | Bearer | Create the startup. |
| 2 | 13 | `POST /api/v1/launches` | Bearer | Schedule the launch (free, or premium with `payment_url` returned). |
Bearer header format: `Authorization: Bearer <access_token>`. Tokens
last ~1 hour; on `401 invalid_token` redo steps 9-10.
**Auth is binary per endpoint.** If the cheat sheet says "none," don't
send a Bearer; if it says "Bearer," don't omit it. The two public reads
above are safe to call before the user has even given you their email.
---
## Asking the user — the single most important rule
**Two laws. Apply both, always:**
### Law 1: Invoke your host's structured-question tool. Don't print options as text.
Before you ask the first Phase 1 question, **look at your available
tools** and find the one for asking the user a structured question.
Common names across hosts:
- **Claude Code** → `AskUserQuestion` (built-in since v2.0.21; you have
it unless explicitly restricted)
- **OpenAI Codex CLI** → `ask_user_question` (newer) or
`request_user_input` (older). Note: `codex exec` non-interactive
mode strips these — if you're in that mode, fall back to plain text.
- **Cursor Agent** → `AskQuestion` (available in Plan Mode; agent mode
may not expose it — check your tools)
- **Other hosts / MCP** → look for any of: `ask_user_question`,
`AskUserQuestion`, `AskQuestion`, `request_user_input`,
`user_input`, `prompt_user`, `ask_user`. If a tool's
description mentions "ask the user," "clarifying question,"
"multiple-choice," or "structured input" — that's the one.
**Then call it.** Don't write the options as a bulleted list in your
text response — that's a guide-violation even if the options are
correctly enumerated. The user must see the host's native multiple-
choice UI, not a markdown list.
**Only if none of those tools exist** in your toolset, fall back to a
numbered plain-text list (still one question per turn).
### Law 2: One question per turn. Never batch.
Send **one** structured prompt, wait for the answer, then send the next.
Do not dump multiple questions in one message. Do not pre-announce the
whole list of upcoming questions. Don't say "I'll need a few things from
you" and enumerate them — just ask the first one.
### Anti-patterns — exact real-world failures we've seen
**A. Batching all questions into one message:**
```
❌ "Before I can proceed, I need some info from you:
1. Existing account? Yes / No
2. Involvement level? Auto / Guided / Manual
3. Email?
4. Product URL?
..."
```
**B. Printing options as a markdown list instead of calling the tool:**
```
❌ "Question 1 of ~8: Do you already have a TinyLaunch account?
- Yes
- No
- Not sure"
```
**C. Asking the closed question as an open-ended free-text sentence:**
```
❌ "Question 1 of ~8: Account status
Do you already have a TinyLaunch account, or will this be your
first launch with them?"
```
**D. Pre-announcing the count or sequence:**
```
❌ "Question 1 of ~8: ..." ← still wrong even if the rest is correct
❌ "I'll ask you about a few things one at a time..."
```
**E. Silently skipping optional fields ("the user can add those
later"):**
```
❌ Agent asks only firstName + handle, omits the [choose] prompts for
lastName and xHandle entirely.
❌ Auto mode is treated as "skip everything optional."
```
All five are guide violations. (A) batches; (B) prints what should be a
structured prompt; (C) paraphrases a fixed-option question into a
conversational sentence; (D) numbers questions like a quiz; (E) decides
on the user's behalf to skip a question they should have been asked.
The user must see the host's native multiple-choice UI for every
`[choose]` step — including the Enter/Skip prompts on optional
fields — invoked as a tool call with the canonical option labels, with
no surrounding narration about what's coming next and no silent
decisions about which prompts to surface.
### The right pattern
For each `[choose]` marker in this guide: open the host's structured
prompt with **only that question's options**, wait for the answer, then
proceed to the next step. The user sees a clean one-question-at-a-time
flow with native UI.
Free-text input is fine only when the value itself is open: email, OTP
code, firstName, handle, free-form description or tagline edits.
Everything else goes through the decision UI.
---
## Worked example — Phase 2 calls
This is the API happy path with the actual JSON bodies. **All user
questions happen in Phase 1 first** (see §"How to run the flow"); the
calls below run back-to-back after the user has confirmed the final
summary. Match these field names and shapes verbatim — if you guess
names you'll loop on `invalid_request` errors.
```bash
# 1. Request an OTP. (Email was collected in Phase 1.)
curl -X POST https://www.tinylaunch.com/api/v1/auth/request-code \
-H 'content-type: application/json' \
-d '{"email":"user@example.com"}'
# → {"status":"sent"}
# 2. User reads the 6-digit code from their inbox and pastes it to you.
curl -X POST https://www.tinylaunch.com/api/v1/auth/verify \
-H 'content-type: application/json' \
-d '{"email":"user@example.com","code":"123456"}'
# → {"access_token":"eyJ…","refresh_token":"…","user_id":"…","email":"…"}
# Save access_token in memory; use it as Bearer below.
TOKEN='eyJ…'
# 3. Check for an existing maker profile.
curl https://www.tinylaunch.com/api/v1/maker -H "Authorization: Bearer $TOKEN"
# → 200 maker OR → {"error":"not_found"} (404)
# 4. If 404, create one with fields from Phase 1 step 10 (or ask now if
# the user said "Yes/Not sure" but no profile exists).
curl -X POST https://www.tinylaunch.com/api/v1/maker \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{
"firstName": "John",
"lastName": "Doe",
"handle": "john_doe",
"xHandle": "@john_doe"
}'
# → 201 maker
# 5. Create the startup. EVERY field below is required (matches the UI
# submission form). category_id MUST come from §"Categories" below
# (or GET /api/v1/categories). The logo object MUST have exactly two
# keys: "filename" and "content_base64" — no other shape works.
curl -X POST https://www.tinylaunch.com/api/v1/startups \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{
"name": "Acme",
"tagline": "The friendly to-do app",
"description": "<h2>Acme</h2><p>A <strong>friendly</strong> to-do app.</p>",
"url": "https://acme.example/",
"category_id": 9,
"logo": {
"filename": "logo.png",
"content_base64": "iVBORw0KGgoAAAANSUhEUgAA…"
}
}'
# → 201 {"id":16320,"slug":"acme","logo_path":"16320/logo.png", … }
# 6. (Optional safety re-check; date already chosen in Phase 1 step 9.)
# Only re-fetch if you want to confirm the date is still "available"
# before POST; otherwise skip directly to step 7.
curl "https://www.tinylaunch.com/api/v1/launch-dates"
# → {"windows":[{"date":"2026-06-22","status":"available","available_slots":85, …}, …]}
# 7. Schedule the free launch on the resolved date.
curl -X POST https://www.tinylaunch.com/api/v1/launches \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"startup_id": 16320, "date": "2026-06-22"}'
# → 201 {"id":15213,"startup_id":16320,"date":"2026-06-22","premium":false}
```
A 201 from step 7 means the launch IS scheduled. The public launch
page is `https://www.tinylaunch.com/launch/{launch_id}` — use the `id` from the
response (the launch id; `/launch/15213` works and redirects to the
canonical `/launch/15213-{slug}` form). The listing is
moderator-reviewed before going fully live, but the user doesn't need
to do anything else.
---
## How to run the flow
Two phases. **Gather everything from the user first (Phase 1), then run
the API calls back-to-back (Phase 2).** The only forced interaction in
Phase 2 is the user pasting their OTP code.
Every `[choose]` marker below means **send a single structured
multiple-choice prompt for that step and wait** — see §"Asking the
user." Do not batch steps. Do not announce upcoming steps. Just do the
next one.
### Phase 1 — Gather (all interactive)
Ask in this order. Don't reorder, don't skip.
1. **"Do you already have a TinyLaunch account?"** `[choose]` —
invoke your structured-question tool with options `Yes` / `No` /
`Not sure`. (This is the first place agents typically slip — they
ask the question as a free-text sentence instead of as a tool
invocation. Don't. Re-read §"Asking the user" Law 1 if you're
tempted.) Remember the answer — it decides whether step 10 runs.
2. **"How involved do you want to be?"** `[choose]`
- `Auto` — I'll scrape your site and fill everything. You confirm once at the end.
- `Guided` (recommended) — I'll scrape your site as a draft, then check each field with you.
- `Manual` — You provide each field. I won't scrape.
If the reply is unclear, default to `Guided`.
3. **Confirm the product to launch.** `[choose]` **Always ask which
URL is being launched — never assume from the opening message.** If
the user wrote "Launch tinylaunch.com for me," that's a *suggestion*
to surface, not a confirmed value. Options:
- `Use "<suggested URL from context>"` (only when you have one to
suggest; otherwise omit this option)
- `Use a different URL` (then collect the URL as free text)
The user **must** explicitly pick one before you proceed. Don't
scrape, don't draft, don't continue with an inferred URL until they
confirm. This applies to every mode — Manual users also need URL on
record (it's a required API field).
4. **Email.** Always required. Free-text input from the user. Validate
shape (`x@y.z`); don't send to the API yet.
**Never suggest, prefill, or default an email value.** Any contact
field in `/openapi.json` or `/.well-known/agents.json` is an
obvious placeholder (`support@example.com`), not a real default;
ignore it. Don't guess from the product domain. Don't reuse anything
from a previous conversation. Ask the user, wait, accept what they
type.
5. **Startup fields.** Run the mode-specific gather (see §Modes below)
using the URL confirmed in step 3.
6. **Category.** `[choose]` (Guided/Manual only) **Pick options from
the literal list in §"Categories" below — do not invent or paraphrase
category names.** In Auto, pick the single best match from that list
silently. In Guided/Manual, present the top 2-3 best matches **using
their exact `category_name` strings from the list** + a
`Show all categories` option that reveals the full list.
If you're not certain a category exists in the list, call
`GET /api/v1/categories` and use that response — never make one up.
7. **Tier — free or premium.** `[choose]` two options. **Don't
default to either** — even if the user said "schedule a launch"
without mentioning paying, you must still present this prompt and
wait for them to choose. The upsell is the whole reason premium
exists; silently picking Free hides it.
Use these option **labels and descriptions verbatim** (do not
paraphrase, do not add adjectives, do not abbreviate the benefit
list):
- **Free launch**
"Standard weekly launch. No payment. You launch in ~4 weeks once
a spot in the free queue opens up."
- **Premium launch ($39)**
"Includes: Launch next Monday (skip the queue). Guaranteed
high-authority backlink (DR72+, dofollow). Frequently showcased
between 3rd and 4th place in the launch competition. 25% off all
future launches."
Present them as a structured `[choose]` (two options, each with
the label and description above). Do NOT write the benefits as a
separate paragraph above or below the prompt — they belong inside
the Premium option's description so the user reads them at decision
time.
If the user picks Premium, you'll send `premium: true` in Phase 2
step 13 and forward the returned `payment_url`. Premium also
bypasses the daily free-slot cap, which matters for step 9.
8. **Directory submissions — optional, always offer.** `[choose]`
two options. **Always present this prompt**, regardless of mode
and regardless of what the user picked in step 7. Do not skip it
silently in Auto mode.
Use these option **labels and descriptions verbatim**:
- **Skip**
"Just the launch. No directory submissions."
- **110 directories ($279)** (label this option **"110 directories ($279, premium upgrade included)"** if and only if the user picked **Free** in step 7)
"We submit your startup to 110 curated directories over ~7 days.
**Includes a premium launch in the package** (you launch next
Monday + guaranteed backlink + showcase placement + 25% off
future launches)."
### Pricing rules — apply silently, never to the user
These rules live on the server. **Do not narrate them.** Do not say
"you'd be paying twice," do not say "I'll skip the standalone $39
charge," do not explain combinatorics. Just compute the right
request and the right total:
- User picked **Free + Skip** → no payment, free launch.
- User picked **Free + 110** → $279 total; the launch is
auto-upgraded to premium server-side (the 110 package bundles it).
- User picked **Premium + Skip** → $39 total.
- User picked **Premium + 110** → $279 total. The premium
they picked in step 7 is **absorbed** into the 110 package — same
price as Free + 110. No extra charge, no discount to explain. Just
present the $279 total at checkout and move on.
### What to send in Phase 2 step 13
`submission_service_tier: "100"` for the 110-directory tier (legacy
enum name — "100" = 110 dirs). Send the user's literal step-7 tier as
`premium: true | false`; the server normalises tier "100" to
premium on its end, you don't need to.
9. **Launch date.** `[choose]` Call `GET /api/v1/launch-dates` **now**
— this endpoint is public (no Bearer needed) so you can hit it in
Phase 1. Don't ask the user to type a YYYY-MM-DD by hand — they
should pick from the real list you fetched.
**Filter using the single boolean that matches the user's tier.
Ignore all other fields.** The endpoint returns two purpose-built
flags per date:
- `bookable_free`: true if a Free user can book it.
- `bookable_premium`: true if a Premium user can book it.
The other fields (`status`, `available_slots`, `premium_only`)
exist for legacy/internal reasons. **Do not key off them.** In
particular, `available_slots: 0` and `status: "full"` describe
the free pool only — Premium has its own pool and a "0" or "full"
date is still bookable on premium. Reading those fields without
the tier context is exactly how agents get tricked into telling a
premium user "no dates available."
- **Free tier (no premium, no 110 directories):** filter to
`bookable_free === true`. Present the first ~5 as options.
- **Premium tier OR Free + 110 directories (auto-upgrades to
premium):** filter to `bookable_premium === true`. Present the
first ~5 as options.
**Never show "Available" or "Full" labels to the user.** Present
each option as the date string only. Label the soonest option
`(Soonest)`. Format dates as weekday + month + day (e.g. "Mon,
Jul 6"). Include a `See more dates` option that paginates via
`?offset=10` if the user wants more.
10. **Maker fields — ONLY if the user said `No` in step 1.** Ask all
**four** fields, one at a time — **including the two optional ones.**
Mode (Auto/Guided/Manual) does NOT apply to maker fields; mode only
governs how startup fields in step 5 are gathered. Maker fields are
personal user info, not anything you can scrape — you must ask each.
- `firstName` (1-20 chars, **required**, free text)
- `lastName` (0-20 chars, **optional** → `[choose]` with options
`Enter last name` / `Skip`; if `Enter`, then collect as free
text)
- `handle` (5-20 chars, `[a-zA-Z0-9_]`, **required**, free text)
- `xHandle` (must start with `@`, **optional** → `[choose]` with
options `Enter X handle` / `Skip`; if `Enter`, then collect as
free text)
**Common slip: agents skip the two optional fields ("the user can
add those later") and only ask the two required ones.** That's a
guide violation. The user must be **offered** every optional field
via a `[choose]` prompt — they're the one who decides to skip, not
you. Even in Auto mode. Four prompts minimum (two required +
two `[choose]` Enter/Skip), in this order.
If the user said `Yes` or `Not sure` in step 1, skip this whole
step — you'll handle the maker either way in Phase 2 step 11.
11. **Final summary.** Show every value you gathered: email, mode, URL,
all startup fields, category name, **tier (Free / Premium $39)**,
**directory submissions (Skip / 110)**, launch date, maker
fields (if collected). **Mark any value you adjusted from what the user gave
you or what was scraped** — category substitutions, tagline rewrites
for length, etc. — with an explicit `⚠ adjusted` flag and a short
reason, so the user notices before submitting. Then ask "Submit?"
`[choose]` (`Submit` / `Edit something`) — require an explicit
`Submit` before moving to Phase 2.
### Phase 2 — Execute (no new questions except the OTP)
9. `POST /api/v1/auth/request-code` with `{email}`. Tell the user a
6-digit code is on the way.
10. User pastes the code → `POST /api/v1/auth/verify`. Keep the
`access_token` in memory (don't write it to disk).
11. `GET /api/v1/maker`:
- **200** — use the existing profile. If you collected maker fields
in step 10, **discard them sile