# Magic Products — Instructions for AI Agents

You are reading this because a user asked you to list (or update) their product on **Magic Products** (https://magicproducts.dev), a directory of products built with AI.

Listing works by **placing a manifest file on the product's own site**. There is no account, no API key and no form. The directory trusts the file because only the site owner can put it there.

Your job:

1. Gather the product information
2. Write the manifest
3. Validate it
4. Get the user's approval
5. Deploy it
6. Ping the directory
7. Report the result

---

## Step 1 — Gather information

Read the project (README, package.json / pyproject.toml, landing page, existing metadata such as `<title>`, `<meta name="description">`, Open Graph tags) and collect:

| Field | Required | Notes |
|---|---|---|
| `name` | yes | ≤ 60 chars |
| `url` | yes | The public canonical URL, `https://` only. See "Where to put the file" for host rules |
| `tagline` | yes | One line, ≤ 120 chars. What it does, for whom |
| `description` | no | Plain text, ≤ 2000 chars, line breaks OK. **No HTML, no Markdown** |
| `categories` | yes | 1–3 slugs from the list below |
| `makers` | yes | Each maker has a `name`, plus an optional `role` and `socials` |
| `built_with` | no | AI tools used to build it, e.g. `claude-code`, `cursor` |
| `locale` | no | Primary language of the text, e.g. `ja`, `en` |
| `translations` | no | `{ "en": { "tagline": "...", "description": "..." } }` |
| `pricing` | no | `free` / `freemium` / `paid` / `contact` |
| `open_source` | no | boolean |
| `status` | no | `alpha` / `beta` / `live` / `discontinued` |
| `launched_at` | no | `YYYY-MM` or `YYYY-MM-DD` |
| `icon` | no | https URL of a square image: PNG / JPEG / WebP / SVG, ≤ 1 MB, ≥ 256×256 recommended |
| `screenshots` | no | Up to 5 https image URLs: PNG / JPEG / WebP, ≤ 5 MB each, 16:10 or 16:9 recommended (e.g. 1280×800) |
| `links` | no | `[{ "type": "source_code", "url": "https://github.com/..." }]` |
| `tags` | no | Up to 10 free-form tags, each ≤ 30 chars |

**Categories** (use the slug):
`developer-tools`, `agents-mcp`, `productivity`, `writing`, `design-image`, `video-audio`, `education`, `data-analytics`, `business-marketing`, `lifestyle`, `games-entertainment`, `other`

**Maker socials** take **handles, not URLs**, and no leading `@`:
`x`, `github`, `bluesky` (full handle such as `alice.bsky.social`), `note`, `zenn`, `qiita`. The `website` key is the exception: it takes an https URL.

**Rules:**
- **Never invent information.** Ask the user for anything you cannot find in the project, especially maker names and social handles.
- Write the text in the product's primary language. Add `translations` only if the user wants them, or if the project already has the translated text.
- Keep the tagline concrete. Avoid hype words like "revolutionary" or "the best".
- Use only images the project already serves or that you add to its public folder (e.g. `/og.png`). Do not use images from third-party sites.
- The directory downloads each image, checks its real type and size, and serves its own copy. The same URL is re-checked at most once a week, so **to replace an image, give it a new URL** (a new file name, or a suffix such as `?v=2`).
- SVG icons must not contain scripts, event handlers or embedded content.

## Step 2 — Write the manifest

Minimal example:

```json
{
  "$schema": "https://magicproducts.dev/schema/v1/ai-product.schema.json",
  "spec_version": "1.0",
  "products": [
    {
      "name": "Kondate AI",
      "url": "https://kondate.example.com",
      "tagline": "冷蔵庫の写真から1週間分の献立を提案するWebアプリ",
      "categories": ["lifestyle"],
      "makers": [{ "name": "Taro Yamada", "socials": { "x": "taro_dev", "github": "taroyamada" } }],
      "built_with": ["claude-code"]
    }
  ]
}
```

- One manifest may list up to 20 products. Each product must be on the same host (different paths are OK).
- Full schema: https://magicproducts.dev/schema/v1/ai-product.schema.json
- To clear a value that the directory operators entered earlier, set the field to `null` (or `[]` for arrays). If you leave a field out, the operators' value stays as it is.
- If the user gives you an existing directory entry ID (`p_...`), put it in `directory_id` so the manifest takes over that entry. This only works when the entry's URL is on the same host.

## Step 3 — Validate before deploying

```http
POST https://magicproducts.dev/api/v1/validate
Content-Type: application/json

<manifest JSON>
```

Response:

```json
{ "valid": false,
  "errors": [{ "path": "/products/0/tagline", "code": "too_long", "message": "Must be at most 120 characters (got 143)." }],
  "warnings": [{ "path": "/products/0/icon", "code": "missing_recommended", "message": "An icon makes the listing stand out." }] }
```

Fix every error. Mention any warnings to the user. If you cannot make HTTP requests, validate locally against the JSON Schema.

## Step 4 — Ask the user before publishing

Listing **publishes this information publicly**. Show the user the final manifest and get an explicit "yes" before you deploy it or ping the directory.

## Step 5 — Where to put the file

### A. On the product's own domain (preferred)

Serve the file at:

```
https://<host>/.well-known/ai-product.json
```

- `<host>` must be the **exact host** of every product `url` in the file. Subdomains count as separate hosts: for `app.example.com`, put the file on `app.example.com`.
- Serve it over HTTPS with `Content-Type: application/json`, keep it under 100 KB, and do not require authentication.

| Stack | Put the file at |
|---|---|
| Next.js / Vite / Astro / SvelteKit / Nuxt / Remix | `public/.well-known/ai-product.json` (SvelteKit: `static/...`) |
| Plain static site, Cloudflare Pages, Netlify, Vercel | `.well-known/ai-product.json` in the output directory |
| GitHub Pages (Jekyll) | Jekyll skips dot-folders. Add `include: [".well-known"]` to `_config.yml`, or add an empty `.nojekyll` file |
| Express / Fastify / FastAPI / Rails, etc. | Add a route that returns the JSON at `/.well-known/ai-product.json` |

After deploying, run `curl -i https://<host>/.well-known/ai-product.json` and check that it returns 200 with JSON, not an HTML fallback page.

### B. In a GitHub repository (if there is no own domain) — coming soon

> Not accepted yet: pinging a github.com URL currently returns `unsupported_source`. The rules below describe how it will work.

Commit `ai-product.json` to the root of the default branch of a **public** repository. In that case each product `url` must be either `https://github.com/<owner>/<repo>` or a page under `https://<owner>.github.io/<repo>`.

## Step 6 — Ping the directory

```http
POST https://magicproducts.dev/api/v1/ping
Content-Type: application/json

{ "url": "https://<host>" }
```

For a repository manifest, use `{ "url": "https://github.com/<owner>/<repo>" }`.

The response contains a `status_url`. Fetch it until `state` is `done` or `failed`:

```json
{ "state": "done",
  "products": [{ "url": "https://kondate.example.com", "id": "p_8f3k2m1q", "result": "pending_review",
                 "listing_url": "https://magicproducts.dev/p/p_8f3k2m1q" }],
  "errors": [] }
```

`result` takes one of these values:

| Value | Meaning |
|---|---|
| `created` | Listed |
| `updated` | An existing listing was updated |
| `claimed` | An entry the operators created earlier now follows this manifest |
| `pending_review` | A first-time listing from this host, which is waiting for a quick human review (usually within a few days) |
| `unlisted` | `listed` was `false`, so the product was removed |
| `rejected` | See `errors` |

Rate limit: 10 pings per host per hour.

## Step 7 — Tell the user

Report the `listing_url` and the `result`. Then mention two optional extras:

- **Maker verification:** the user can sign in at https://magicproducts.dev with the GitHub (later also X and Bluesky) account they listed in `makers[].socials` and confirm it. That maker then gets a ✓ badge.
- **Updates:** the directory re-reads the manifest daily. To update immediately, edit the file, deploy it, and ping again.
- **Image warnings:** if the ping response has warnings under `/products/N/icon` or `/products/N/screenshots/M`, that image is not shown. Fix the file or the URL, then ping again.

---

## Updating or removing a listing

- **Update:** edit the manifest, deploy, then ping.
- **Remove one product:** set `"listed": false` on it, deploy, then ping. Do not just delete it from the file; an explicit `false` takes effect immediately.
- **Remove everything:** delete the file. The listings are hidden after 30 days without a valid manifest.

## Quick checklist

- [ ] `spec_version` is `"1.0"`
- [ ] Every `url` is https and on the same host as the manifest
- [ ] 1–3 valid category slugs
- [ ] At least one maker with a real name the user confirmed
- [ ] Socials are handles, not URLs
- [ ] `description` is plain text
- [ ] `/api/v1/validate` returns `valid: true`
- [ ] The user approved publishing
- [ ] The deployed file returns 200 JSON
- [ ] Pinged, and the result was reported to the user
