# 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": "", "detail"?: "", ... }`. 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 | 7 | `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 `. 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 8 (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": "Ada", "lastName": "Lovelace", "handle": "ada_lovelace", "xHandle": "@ada" }' # → 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": "

Acme

A friendly to-do app.

", "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 7.) # 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, **not** the startup slug; URLs are `/launch/15213`, not `/launch/tinyshots`). 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 8 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 ""` (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. Validate shape (`x@y.z`); don't send to the API yet. 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. **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. Present the next ~5 dates where `status: "available"` as options, with the soonest one labeled `(Soonest)`. Include a `See more dates` option that paginates via `?offset=10`. Don't ask the user to type a YYYY-MM-DD by hand — they should pick from the real list of available dates you fetched. 8. **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. 9. **Final summary.** Show every value you gathered: email, mode, URL, all startup fields, category name, 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 8, **discard them silently** (don't PATCH). Mention "using your existing profile @" in step 14. - **404** — if you have maker fields from step 8, `POST /api/v1/maker` with them. If you don't (user said `Yes` or `Not sure` and was wrong), ask firstName + handle now (plain text — these values are open), then POST. 12. `POST /api/v1/startups` with everything gathered. Read `id`, `slug`, `logo_path` from the response. 13. `POST /api/v1/launches` with `{startup_id, date}` (the date the user picked from the real list in Phase 1 step 7). If the response is `date_full` (409) — slots filled between gather and submit — re-call `GET /api/v1/launch-dates` and `[choose]` from the new available list, then retry the POST. 14. **Report:** maker handle (new or existing), startup name + the user's own URL, launch date, and the public launch page `https://www.tinylaunch.com/launch/{launch_id}` (use the `id` returned by step 13's `POST /launches` — it's a number; the URL is **not** built from the startup slug). Don't mention payment, login, or any other "next steps." --- ## Modes The mode chosen in Phase 1 step 2 governs **only** how step 5 (startup fields) gets gathered, using the URL confirmed in step 3. **The same Phase 2 calls run regardless of mode.** Mode does **NOT** affect: - The account question (step 1) — always `[choose]`. - URL confirmation (step 3) — always `[choose]`. - Email (step 4) — always asked. - Category picker (step 6) — Guided/Manual show `[choose]`; Auto picks silently per the substitution rule below. - Launch-date picker (step 7) — always `[choose]`. - **Maker fields (step 8) — always ask all four, including the two optional ones via `[choose]` Enter/Skip. Auto does not skip them.** - Final summary (step 9) — always `[choose]` Submit / Edit. ### Auto Scrape the URL once and extract (all fields **required** at the API): - `name` (1-30 chars, trim cleanly) - `tagline` (1-60 chars; if scraped value is longer, generate a ≤60-char rewrite that preserves the meaning — note the rewrite in the final summary) - `description` (1-10000 chars, HTML, see §"Description HTML") - `logo` (PNG/JPG/WebP, ≤209715 bytes; see §"Logo guidance") - `category` — pick the single best match **from §"Categories"** (never invent a category name). **Only pick silently if a category in the list is an obvious, high-confidence match for the product type.** If you'd have to stretch (the product's natural category isn't represented and you're picking the "closest" one from a different domain — e.g., a launch-directory product → "Startup & Small Business"), **stop and `[choose]`** between your top 3 candidates before continuing. Don't silently substitute across domains. **If any required field can't be scraped, stop and ask the user.** Logo missing from the site → ask. No usable tagline text → ask. Description can't be derived → ask. Never submit without; never fabricate placeholder values. Don't ask field-by-field for fields you successfully scraped. Every scraped value lands in the final summary (step 9) where the user can override before submit. ### Guided (default) Scrape the URL once as a draft. For each startup field, present `[choose]`: - `Keep ""` - `Edit` (then collect the new value as free text) **Skip is not an option for startup fields** — all six (name, tagline, description, url, logo, category_id) are required to match the UI. If a scraped value is empty, the only options are `Edit` (collect from user) or re-scrape. For category, `[choose]` from the top 2-3 best matches **using exact `category_name` strings from §"Categories"** + `Show all categories`. Never present a category that isn't in the list. ### Manual Don't scrape. Don't infer. Ask each startup field directly — **all six are required** (name, tagline, description, url, logo, category_id), no skips. Collect each as free text (or file upload for logo); use the `[choose]` category picker from §"Categories" for category. Same date picker as Guided. ### Rules for every mode - **Never fabricate.** If a value isn't available, ask. - **The final summary in step 9 is mandatory.** Even in Auto, get an explicit `go` before any Phase 2 call. - **Email, OTP code, firstName, handle, xHandle always come from the user.** Never guess these. --- ## 1. Authentication (email OTP) ### Request a code ```bash curl -X POST https://www.tinylaunch.com/api/v1/auth/request-code \ -H 'content-type: application/json' \ -d '{"email":"user@example.com"}' ``` Always returns `{"status":"sent"}` — the actual email may or may not have gone out (we intentionally don't leak account existence). ### Verify and receive a bearer token ```bash curl -X POST https://www.tinylaunch.com/api/v1/auth/verify \ -H 'content-type: application/json' \ -d '{"email":"user@example.com","code":"123456"}' ``` ```json { "access_token": "eyJhbGciOi…", "refresh_token": "vacur3…", "user_id": "cd7c40b8-…", "email": "user@example.com" } ``` Use `Authorization: Bearer ` on every subsequent call. Tokens currently last ~1 hour. If you get `401 invalid_token`, redo steps 1-3 in §"How to run the flow". | Code | Status | Meaning | | ---- | ------ | ------- | | `invalid_request` | 400 | Bad email or code shape. | | `verification_failed` | 401 | Wrong/expired code — ask the user to read it again. | | `missing_bearer_token` | 401 | Authorization header absent on a protected route. | | `invalid_token` | 401 | Bearer token expired — redo the OTP flow. | --- ## 2. Maker profile A maker profile is the user's identity on TinyLaunch. Every startup belongs to exactly one maker. The user only needs one — create it once, reuse forever. - `GET https://www.tinylaunch.com/api/v1/maker` → 200 maker or 404 `not_found`. - `POST https://www.tinylaunch.com/api/v1/maker` (only if 404 above): ```json { "firstName": "Ada", // 1-20 chars, required "lastName": "Lovelace", // 0-20 chars, optional "handle": "ada_lovelace", // 5-20 chars, [a-zA-Z0-9_], unique "xHandle": "@ada" // optional, must start with @ } ``` - `PATCH https://www.tinylaunch.com/api/v1/maker` for updates. All fields optional. Errors: `maker_exists` (409) if you POST when one already exists — use PATCH instead. `handle_taken` (409) if the handle is in use. --- ## 3. Startup ### Create a startup ```bash curl -X POST https://www.tinylaunch.com/api/v1/startups \ -H "Authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d @startup.json ``` Body (every field is required at POST — matches the UI submission form): ```json { "name": "Acme", // required, 1-30 chars "tagline": "The friendly to-do app", // required, 1-60 chars "description": "

Acme

A friendly to-do app.

", // required, 1-10000 chars, HTML "url": "https://acme.example/", // required, http(s) "category_id": 1, // required, see §"Categories" "logo": { // required "filename": "logo.png", "content_base64": "iVBORw0KGgo…" } } ``` Returns 201 with the created startup. Response includes `slug`, `category_name`, `logo_path` — all filled in by the server. ### Get / update one - `GET https://www.tinylaunch.com/api/v1/startups` → list of your startups. - `GET https://www.tinylaunch.com/api/v1/startups/{id}` → one startup. - `PATCH https://www.tinylaunch.com/api/v1/startups/{id}` → partial update. Same fields as POST, all optional. Passing `logo` replaces the previous file. ### Description HTML `description` is HTML (not markdown). Use these tags only: - `

`, `
` - `

`, `

`, `

` - `` / ``, `` / ``, ``, `` - `
    `, `
      `, `
    1. ` Tags outside this list are stripped server-side. No inline styles, no scripts, no links, no embeds. ### Logo guidance The `logo` field **must be an object with exactly two keys**: `{"filename": ".png", "content_base64": ""}`. Any other shape returns `invalid_request` with `{"logo":["Required"]}` — don't loop guessing alternative key names. File must be PNG, JPG, or WebP under 209715 bytes (~200 KB). **Finding a logo from a URL.** Try these in order — don't probe random paths: 1. Parse the site HTML for `` and fetch the exact href. 2. Then `` from the HTML. If neither hit returns a real image, **ask the user to provide one**. Don't fall back to a tiny favicon, don't use `og:image` (it's a social-share banner, not a logo), and don't generate a placeholder. Always show the user what logo you picked **before** sending the create request. ### Startup errors | Code | Status | What to do | | ---- | ------ | ---------- | | `invalid_request` | 400 | Zod field errors in `detail`. Fix the field. | | `invalid_category` | 400 | The id you sent isn't in the list. The response includes the full `categories` array — pick a real one. | | `invalid_logo_format` | 400 | Wrong extension. Convert to PNG/JPG/WebP. | | `invalid_logo_encoding` | 400 | Bad base64. Re-encode the bytes. | | `logo_too_large` | 413 | Logo > `max_bytes`. Ask the user for a smaller file. | | `no_maker_profile` | 409 | Create the maker profile (§2) first. | | `not_found` | 404 | Wrong startup id. | | `forbidden` | 403 | Not your startup. Don't retry — tell the user. | | `invalid_id` | 400 | Path id is not a positive integer. | --- ## 4. Launches (free & premium) A launch is a startup + a launch date. Both **free** and **premium** ($39) launches are bookable over the API. Premium is a one-time $39 payment; the user clicks a checkout link the API hands back. The launch is held but inactive until payment lands. **What premium includes — mention these four items exactly, verbatim, when offering the user the choice:** - Launch next Monday - Guaranteed high-authority backlink - Frequently showcased between 3rd and 4th place in the launch competition - 25% off all future launches ### List upcoming launch windows ```bash curl "https://www.tinylaunch.com/api/v1/launch-dates" -H "Authorization: Bearer $TOKEN" ``` ```json { "windows": [ { "date": "2026-05-25", "available_slots": 0, "status": "full", "premium_only": true }, { "date": "2026-05-26", "available_slots": 12, "status": "available", "premium_only": false }, { "date": "2026-05-27", "available_slots": 85, "status": "available", "premium_only": false } ], "offset": 0, "page_size": 10, "next_offset": 10 } ``` Page size is fixed at 10. Paginate with `?offset=N` (0..200) — pass `next_offset` from the previous response. Surface only `status: "available"` dates to the user; `status: "full"` dates can't host another free launch. ### Schedule a free launch ```bash curl -X POST https://www.tinylaunch.com/api/v1/launches \ -H "Authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"startup_id": 12345, "date": "2026-05-27"}' ``` Returns 201 with the launch row. ### Schedule a premium ($39) launch Add `"premium": true` to the same endpoint. The response includes a `payment_url` — give that link to the user. The launch is **not active until they pay**; the row stays `paid:false` until our payment webhook flips it. Don't poll, just hand the user the link and stop. When you hand over the link, tell the user **exactly** this: *"Here's your payment link: . After you pay you'll get a confirmation email within 5–10 minutes — that's when your launch is officially scheduled. You don't need to come back here."* Don't promise anything else (no "I'll check back", no "let me know when done"). The webhook does the rest. ```bash curl -X POST https://www.tinylaunch.com/api/v1/launches \ -H "Authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"startup_id": 12345, "date": "2026-05-27", "premium": true}' ``` ```json { "id": 15214, "startup_id": 12345, "date": "2026-05-27", "premium": true, "paid": false, "payment_url": "https://checkout.dodopayments.com/..." } ``` Premium launches bypass the free-slot cap and the same-day duplicate rule, so `date_full` / `duplicate_launch` won't fire for them. **Free vs premium — how to ask the user:** if they haven't said, present the choice with your host's structured-prompt tool (Free / Premium $39), not as text. Don't default to premium silently. List the four premium benefits above (verbatim) so the user can make an informed pick. ### Launch errors | Code | Status | What to do | | ---- | ------ | ---------- | | `invalid_request` | 400 | Missing/bad `startup_id` or `date` (must be YYYY-MM-DD). | | `date_not_bookable` | 400 | Date wasn't from `/api/v1/launch-dates`. Re-fetch and pick from there. | | `duplicate_launch` | 409 | This maker already has a free launch on that date. | | `date_full` | 409 | Free slots gone. Ask the user to pick a later date. | | `forbidden` | 403 | Startup belongs to a different user. | --- ## 5. Categories You must pass `category_id`. Pick the single best semantic match — "Productivity" for to-do apps, "AI & Machine Learning" for AI-powered products, etc. If the startup spans multiple, prefer the **most specific** category. The list below is the canonical source; you can also fetch `GET https://www.tinylaunch.com/api/v1/categories` for the same data as JSON (public — no Bearer required). **Business & Finance** - `5` — Finance & FinTech - `7` — HR & Recruitment - `6` — Marketing & Sales - `8` — Startup & Small Business **Consumer & Lifestyle** - `11` — Education & Learning - `10` — Health & Wellness - `12` — Home & Living - `9` — Productivity **E-commerce & Retail** - `20` — E-commerce - `21` — Logistics & Delivery - `22` — Retail Tech **Entertainment & Creativity** - `15` — Design & Art - `13` — Gaming - `14` — Music & Audio - `16` — Video & Content Creation **Fun & Experimental** - `32` — Beta & Early Access - `33` — DIY & Makers - `31` — Side Projects & Hacks **Hardware & Gadgets** - `26` — Consumer Electronics - `27` — IoT & Connectivity - `28` — Robotics & Automation **Industry-Specific** - `34` — Healthcare & MedTech - `35` — Legal & Compliance - `36` — Real Estate & PropTech **Social & Community** - `18` — Community & Networking - `19` — Diversity & Inclusion - `17` — Social Media **Sustainability & Impact** - `23` — Green Tech - `25` — Nonprofit Tools - `24` — Social Impact **Technology & Software** - `2` — AI & Machine Learning - `3` — Developer Tools - `1` — SaaS & Tools - `4` — Web3 & Blockchain **Travel & Mobility** - `30` — Mobility & Transportation - `29` — Travel & Tourism --- ## 6. Limits (cheat sheet) | Field | Limit | | ----- | ----- | | `maker.firstName` | 1-20 chars | | `maker.lastName` | 0-20 chars | | `maker.handle` | 5-20 chars, `[a-zA-Z0-9_]` | | `maker.xHandle` | starts with `@`, `[a-zA-Z0-9_]` | | `startup.name` | 1-30 chars | | `startup.tagline` | 1-60 chars, **required** | | `startup.description` | 1-10000 chars, restricted HTML (see §3), **required** | | `startup.url` | http(s) only | | `startup.logo` | PNG / JPG / WebP, ≤ 209715 bytes (~200 KB), **required** | | `launch_dates ?offset` | 0..200, fixed page size 10 | --- ## What NOT to do - 🚫 Don't probe other URLs. There's no `/api/v1/categories` lookup-by-name endpoint; use the list in §5. - 🚫 Don't loop over category ids guessing. The list is right above. - 🚫 Don't assume the product URL from the user's opening message. Even if they said "launch acme.com," Phase 1 step 3 still requires explicit confirmation via `[choose]`. Suggest, don't decide. - 🚫 Don't batch Phase 1 questions into one message. One `[choose]` per turn, through the host's structured prompt. See §"Asking the user." - 🚫 Don't pre-announce what you're about to ask ("I'll need a few things from you..."). Just ask the next question. - 🚫 Don't free-text a question that has a fixed option set. Closed options → structured prompt. Always. - 🚫 Don't run any Phase 2 call before Phase 1 is finished and the user has said "go" on the final summary. - 🚫 Don't ask the user questions mid-Phase-2 (except the OTP code, and the maker fallback in step 11 when their step-1 answer was wrong). - 🚫 Don't `PATCH` an existing maker profile on the launch path. If `GET /api/v1/maker` returns 200, discard anything the user typed for firstName/handle and use what's stored. - 🚫 Don't tell the user they need to pay, log in, or do anything else after a 201 from `POST /launches` for a **free** launch. The launch is scheduled; a moderator review happens before it goes fully live, but that's invisible to the user. (For `premium: true`, the opposite — you **must** forward the returned `payment_url`.) - 🚫 Don't construct the public launch URL from the startup slug. The pattern is `/launch/{launch_id}` (numeric), not `/launch/{slug}`. - 🚫 Don't guess the logo field shape — it's `{filename, content_base64}`, full stop. See §"Worked example" for the exact JSON. - 🚫 Don't `POST /api/v1/maker` if `GET` returned 200; use `PATCH`. - 🚫 Don't show the user category IDs — show category names. - 🚫 Don't invent or paraphrase category names. Every option you offer must match a `category_name` from §"Categories" verbatim (or the response of `GET /api/v1/categories`). `"Launch Platform"` is not a category — pick from the actual list. - 🚫 Don't silently omit optional maker fields (`lastName`, `xHandle`). Offer them via `[choose]` with a `Skip` option so the user decides. - 🚫 Don't fetch random pages on `https://www.tinylaunch.com` to introspect data; the API endpoints in this guide are the contract.