# Product Requirements Document: artidrop
_Version: 1.2 | Date: 2026-03-22_
---
## 1. Problem Statement
AI agents (Claude, ChatGPT, LangChain pipelines, custom agents) routinely generate rich artifacts -- HTML pages, Markdown reports, interactive visualizations, dashboards -- but there is no simple, universal way to publish these outputs and get a shareable URL.
Today, users must either:
- Manually copy-paste outputs into a hosting platform (Netlify, Vercel) that requires account setup and project configuration
- Use ephemeral pastebins that expire or don't render HTML
- Rely on platform-specific sharing (Claude Artifacts) that has no programmatic API and only works within that platform's UI
Agents themselves have no built-in "publish" primitive. No major agent framework (LangChain, CrewAI, AutoGen, Google ADK) offers a way to turn an output into a live URL.
## 2. Product Vision
**artidrop is the publishing layer for AI agents.** One command, one API call, or one drag-and-drop -- and any artifact gets a live, shareable URL.
For **consumers**: drag and drop an artifact, get a link to share.
For **developers**: a CLI tool and SDK that agents call to publish automatically.
For **teams**: a dashboard to manage, version, and analyze all artifacts your agents produce.
## 3. Target Users
### Primary: Developers building AI agents
- Building with the Anthropic API, OpenAI API, LangChain, CrewAI, Google ADK, or custom frameworks
- Need their agents to programmatically publish outputs (reports, dashboards, HTML apps) without human intervention
- Value simplicity: one function call or CLI command, no project setup
### Secondary: AI power users (non-developers)
- Use Claude, ChatGPT, or other AI tools daily
- Generate HTML artifacts, Markdown documents, or interactive visualizations
- Want to share outputs with colleagues, clients, or publicly -- without knowing how to deploy a website
- Currently copy-pasting into Google Docs, taking screenshots, or using Claude's built-in publish (limited)
### Tertiary: Teams and organizations
- Multiple agents producing artifacts across projects
- Need centralized management, access control, and analytics
- Want custom domains and branding for published content
## 4. User Journeys
### Journey 1: Consumer drag-and-drop
1. User generates an HTML artifact in Claude / ChatGPT / any AI tool
2. User saves the file locally (or copies the HTML)
3. User opens artidrop.app, signs in with Google (one click)
4. User drags the file onto the page (or pastes HTML)
5. artidrop returns a live URL (e.g., `artidrop.app/a/x7k9m2`)
6. User shares the URL -- recipient sees the rendered artifact immediately
### Journey 2: Developer CLI publish
1. Developer installs: `npm install -g artidrop` (or `pip install artidrop`)
2. Agent generates an HTML file at `./output/report.html`
3. Agent runs: `artidrop publish ./output/report.html`
4. CLI prints: `Published: https://artidrop.app/a/x7k9m2`
5. Agent includes the URL in its response to the user, sends it via Slack, emails it, etc.
6. _First use requires `artidrop login` or setting `ARTIDROP_API_KEY` env var._
### Journey 3: Agent SDK integration
```python
from artidrop import Artidrop
client = Artidrop(api_key="sk-...")
result = client.publish(
content="...
Q1 Revenue Report
...",
title="Q1 Revenue Report",
format="html",
)
print(result.url) # https://artidrop.app/a/x7k9m2
```
### Journey 4: MCP tool (agent discovers and uses artidrop)
1. User configures artidrop MCP server in Claude Code, Cursor, or another MCP-aware client
2. During a conversation, the agent generates an artifact and decides to publish it
3. Agent calls the `artidrop_publish` MCP tool with the HTML content
4. Tool returns the live URL
5. Agent presents the URL to the user
### Journey 5: Team dashboard
1. Team admin creates an artidrop workspace, invites team members
2. Multiple agents across the team publish artifacts using workspace API keys
3. All artifacts appear in the team dashboard -- searchable, filterable by agent/date/tag
4. Admin sets a custom domain (`reports.company.com`) for published artifacts
5. Team members can view analytics (views, unique visitors) per artifact
## 5. Phase 1 Features (MVP) — Detailed Specification
The minimum product that delivers value and validates the concept. Phase 1 ships **three surfaces** (web UI, REST API, CLI) backed by a **single API server** with artifact storage, rendering, versioning, and authentication.
---
### F1. Web Upload UI
#### F1.1 Landing page
The artidrop.app homepage is the primary onboarding surface. It has one job: turn a file or pasted content into a live URL with as little friction as possible.
**Layout (top to bottom):**
1. **Header bar** — logo, tagline ("Instant shareable URLs for AI artifacts"), "Sign in with Google" button (top-right). If authenticated: user avatar, "My Artifacts" link, Sign Out.
2. **Drop zone** (visible only when signed in) — large centered area (minimum 400x300px) with dashed border. States:
- _Default:_ icon + "Drop an HTML or Markdown file here, or click to browse". Below the zone: "or paste content" toggle.
- _Hover (file dragged over):_ border turns solid blue, background lightens, text changes to "Drop to publish".
- _Uploading:_ spinner with "Publishing..." text.
- _Success:_ shows the published URL with a one-click copy button, "Open" link, and QR code. Below: "Preview" iframe showing the rendered artifact.
- _Error:_ red border, error message (e.g., "File too large (max 10MB)", "Unsupported file type").
3. **Paste mode** — toggling "or paste content" reveals a code editor area (monospace, line numbers, syntax highlighting via lightweight library like CodeMirror). Tab toggle: HTML | Markdown. "Publish" button below the editor.
4. **Signed-out state** — when not signed in, the drop zone is replaced by a hero section explaining the product with a prominent "Sign in with Google" CTA. A sample artifact preview can be shown below as social proof.
5. **Footer** — links: Docs, API, CLI, GitHub, Terms, Privacy.
#### F1.2 Accepted inputs
| Input | Method | Behavior |
|---|---|---|
| Single `.html` file | Drag-and-drop or file picker | Publish as HTML artifact |
| Single `.htm` file | Drag-and-drop or file picker | Publish as HTML artifact |
| Single `.md` file | Drag-and-drop or file picker | Publish as Markdown artifact (rendered to HTML at upload time) |
| Single `.markdown` file | Drag-and-drop or file picker | Same as `.md` |
| Pasted HTML string | Paste mode | Publish as HTML artifact. If the string is not wrapped in `` or `Hello World
",
"format": "html",
"title": "My Report",
"visibility": "public"
}
```
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `content` | string | Yes | — | The artifact content (raw HTML or Markdown) |
| `format` | string | Yes | — | `"html"` or `"markdown"` |
| `title` | string | No | `"Untitled"` | Display title (max 200 chars) |
| `visibility` | string | No | `"public"` | `"public"` or `"unlisted"`. (`"private"` deferred to Phase 3) |
**Response (201 Created):**
```json
{
"id": "art_x7k9m2p4",
"url": "https://artidrop.app/a/x7k9m2",
"title": "My Report",
"format": "html",
"visibility": "public",
"version": 1,
"size_bytes": 2048,
"created_at": "2026-03-22T10:00:00Z",
"updated_at": "2026-03-22T10:00:00Z",
"owner": {
"id": "usr_abc123",
"username": "wen"
}
}
```
**Content wrapping:** If `format` is `"html"` and the content does not contain `
{title}
{content}
```
##### GET /v1/artifacts/:id — Get artifact metadata
Returns metadata only (not content). Use the `url` field or `/a/:id/raw` to fetch content.
**Response (200 OK):**
```json
{
"id": "art_x7k9m2p4",
"url": "https://artidrop.app/a/x7k9m2",
"title": "My Report",
"format": "html",
"visibility": "public",
"version": 3,
"size_bytes": 2048,
"created_at": "2026-03-22T10:00:00Z",
"updated_at": "2026-03-23T14:00:00Z",
"owner": {
"id": "usr_abc123",
"username": "wen"
}
}
```
**Access rules:**
- `public` artifacts: anyone can read metadata
- `unlisted` artifacts: anyone with the ID can read metadata (not listed in search/browse)
- Artifacts owned by the authenticated user: always accessible
##### GET /v1/artifacts — List artifacts
Returns the authenticated user's artifacts, paginated.
**Query parameters:**
| Param | Type | Default | Description |
|---|---|---|---|
| `limit` | integer | 20 | Items per page (max 100) |
| `offset` | integer | 0 | Pagination offset |
| `format` | string | — | Filter by `"html"` or `"markdown"` |
| `sort` | string | `"created_at"` | `"created_at"`, `"updated_at"`, `"title"` |
| `order` | string | `"desc"` | `"asc"` or `"desc"` |
**Response (200 OK):**
```json
{
"items": [ /* array of artifact objects */ ],
"total": 42,
"limit": 20,
"offset": 0
}
```
**Requires authentication.** Returns 401 if unauthenticated.
##### PUT /v1/artifacts/:id — Update artifact
Replaces the artifact content, creating a new version. Only the owner can update.
**Request:**
```json
{
"content": "Updated Report
",
"title": "My Updated Report"
}
```
| Field | Type | Required | Description |
|---|---|---|---|
| `content` | string | No | New content. If omitted, content is unchanged (metadata-only update). |
| `format` | string | No | Cannot change format after creation. Returns 400 if provided and different from original. |
| `title` | string | No | New title |
| `visibility` | string | No | Change visibility |
If `content` is provided, `version` increments by 1. If only metadata fields change, version stays the same.
**Response (200 OK):** Updated artifact object.
##### DELETE /v1/artifacts/:id — Delete artifact
Permanently deletes the artifact and all its versions. Only the owner can delete.
**Response (204 No Content)**
##### GET /v1/artifacts/:id/versions — List versions
Returns version history for an artifact.
**Response (200 OK):**
```json
{
"items": [
{
"version": 3,
"size_bytes": 3072,
"created_at": "2026-03-23T14:00:00Z"
},
{
"version": 2,
"size_bytes": 2560,
"created_at": "2026-03-22T18:00:00Z"
},
{
"version": 1,
"size_bytes": 2048,
"created_at": "2026-03-22T10:00:00Z"
}
],
"total": 3
}
```
**Access rules:** same as GET `/v1/artifacts/:id`.
#### F2.3 Error responses
All errors follow a consistent shape:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Content exceeds the maximum size of 10MB.",
"details": {
"field": "content",
"max_bytes": 10485760,
"actual_bytes": 15000000
}
}
}
```
| HTTP Status | Error Code | When |
|---|---|---|
| 400 | `VALIDATION_ERROR` | Invalid input (missing required field, bad format, content too large, title too long) |
| 400 | `FORMAT_CHANGE_NOT_ALLOWED` | Attempting to change artifact format on update |
| 401 | `UNAUTHORIZED` | Missing or invalid API key |
| 403 | `FORBIDDEN` | Authenticated but not the owner of this artifact |
| 404 | `NOT_FOUND` | Artifact ID does not exist |
| 409 | `CONFLICT` | Slug already taken (Phase 2, but reserve the error code) |
| 429 | `RATE_LIMITED` | Too many requests. Response includes `Retry-After` header (seconds). |
| 500 | `INTERNAL_ERROR` | Server error |
#### F2.4 Rate limiting headers
Every response includes:
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1711108800
```
---
### F3. CLI Tool
#### F3.1 Installation
```bash
npm install -g artidrop
```
The CLI is a single npm package. No native dependencies. Requires Node.js >= 18.
#### F3.2 Authentication
The CLI supports two authentication methods, checked in this order:
1. **Environment variable:** `ARTIDROP_API_KEY=sk-...` — best for CI/CD and agent automation
2. **Config file:** `~/.config/artidrop/config.json` — created by `artidrop login`
```bash
# Interactive login: opens browser for Google OAuth, stores token in config file
artidrop login
# Verify authentication
artidrop whoami
# > Authenticated as wen (wen@example.com)
# Logout (deletes config file)
artidrop logout
```
**Config file format** (`~/.config/artidrop/config.json`):
```json
{
"api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"api_url": "https://api.artidrop.app"
}
```
The `api_url` field is for self-hosted users (Phase 3) and defaults to `https://api.artidrop.app` if omitted.
#### F3.3 Commands
##### `artidrop publish `
The core command. Publishes a file and returns a URL.
```bash
# Publish a file
artidrop publish ./report.html
# > https://artidrop.app/a/x7k9m2
# Publish from stdin (must specify --format)
cat report.html | artidrop publish - --format html
# > https://artidrop.app/a/x7k9m2
# With options
artidrop publish ./report.html \
--title "Q1 Revenue Report" \
--visibility unlisted
# > https://artidrop.app/a/p3n8w1 (unlisted)
# Update an existing artifact (creates new version)
artidrop publish ./report-v2.html --update art_x7k9m2p4
# > https://artidrop.app/a/x7k9m2 (version 2)
```
**Flags:**
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
| `--title` | `-t` | string | filename without extension | Artifact title |
| `--format` | `-f` | string | inferred from extension | `html` or `markdown`. Required when reading from stdin. |
| `--visibility` | `-v` | string | `public` | `public` or `unlisted` |
| `--update` | `-u` | string | — | Existing artifact ID to update (creates new version) |
| `--open` | `-o` | boolean | false | Open the URL in the default browser after publishing |
| `--json` | | boolean | false | Output full JSON response instead of just the URL |
| `--copy` | `-c` | boolean | false | Copy the URL to the system clipboard |
**Format inference from file extension:**
- `.html`, `.htm` → `html`
- `.md`, `.markdown` → `markdown`
- No extension or unrecognized → error: "Cannot infer format. Use --format html or --format markdown."
**Output behavior:**
- Default: prints only the URL to stdout (so it can be captured by scripts: `URL=$(artidrop publish ./f.html)`)
- `--json`: prints the full API response as pretty-printed JSON
- `--copy`: prints the URL AND copies to clipboard
- Errors and progress messages go to stderr (never pollute stdout)
**Stdin detection:**
- If the argument is `-` or if stdin is not a TTY (pipe detected), read from stdin
- When reading from stdin, `--format` is required
##### `artidrop list`
Lists the authenticated user's artifacts.
```bash
artidrop list
# ID TITLE FORMAT VERSION CREATED
# art_x7k9m2p4 Q1 Revenue Report html 3 2026-03-22
# art_p3n8w1q2 API Documentation markdown 1 2026-03-21
# art_k8j2m4n6 Dashboard Prototype html 7 2026-03-20
artidrop list --json
# [{ "id": "art_x7k9m2p4", ... }, ...]
artidrop list --limit 5
```
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
| `--limit` | `-l` | integer | 20 | Number of items |
| `--offset` | | integer | 0 | Pagination offset |
| `--format` | `-f` | string | — | Filter by `html` or `markdown` |
| `--json` | | boolean | false | JSON output |
##### `artidrop get `
Shows details of a specific artifact.
```bash
artidrop get art_x7k9m2p4
# Title: Q1 Revenue Report
# URL: https://artidrop.app/a/x7k9m2
# Format: html
# Version: 3
# Visibility: public
# Size: 2.1 KB
# Created: 2026-03-22T10:00:00Z
# Updated: 2026-03-23T14:00:00Z
artidrop get art_x7k9m2p4 --json
```
##### `artidrop delete `
Deletes an artifact permanently.
```bash
artidrop delete art_x7k9m2p4
# Are you sure you want to delete "Q1 Revenue Report"? (y/N) y
# Deleted art_x7k9m2p4
# Skip confirmation
artidrop delete art_x7k9m2p4 --yes
```
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
| `--yes` | `-y` | boolean | false | Skip confirmation prompt |
##### `artidrop versions `
Shows version history.
```bash
artidrop versions art_x7k9m2p4
# VERSION SIZE CREATED
# 3 3.0 KB 2026-03-23T14:00:00Z
# 2 2.5 KB 2026-03-22T18:00:00Z
# 1 2.0 KB 2026-03-22T10:00:00Z
```
##### `artidrop login` / `artidrop logout` / `artidrop whoami`
See F3.2 above.
#### F3.4 Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (network failure, server error) |
| 2 | Validation error (bad input, file not found, unsupported format) |
| 3 | Authentication error (not logged in, invalid API key) |
| 4 | Rate limit exceeded |
#### F3.5 Acceptance criteria
- [ ] `artidrop publish ./file.html` prints a working URL to stdout and exits 0
- [ ] Piping from stdin works: `echo 'Hi
' | artidrop publish - --format html`
- [ ] `--json` flag outputs valid JSON to stdout
- [ ] Errors go to stderr, never stdout (scripts can safely capture `$(artidrop publish ...)`)
- [ ] `--update` creates a new version and returns the same base URL
- [ ] `artidrop list` shows a formatted table with ID, title, format, version, and date
- [ ] `artidrop delete` prompts for confirmation; `--yes` skips it
- [ ] Unauthenticated `publish` command fails with a clear "Please sign in first" message and exit code 3
- [ ] `ARTIDROP_API_KEY` env var takes precedence over config file
- [ ] Readable error messages for common failures (no auth, rate limited, file not found, network error)
---
### F4. Artifact Rendering
#### F4.1 Artifact page layout
When a user visits `artidrop.app/a/:shortId`, they see the **artifact page**. This is the sharable destination.
**Layout:**
```
┌─────────────────────────────────────────────────┐
│ artidrop bar (slim, 40px height) │
│ [logo] "Q1 Revenue Report" [Share] [Copy] │
├─────────────────────────────────────────────────┤
│ │
│ │
│ Rendered artifact content │
│ (full-width sandboxed iframe) │
│ │
│ │
│ │
└─────────────────────────────────────────────────┘
```
**artidrop bar (top bar):**
- artidrop logo (links to artidrop.app)
- Artifact title
- "Share" button: copies URL to clipboard
- Version indicator if version > 1: "v3" with dropdown to view other versions
- "Raw" link: opens `/a/:id/raw` (the source HTML/Markdown)
- If the viewer is the owner: "Edit" link (opens web editor, Phase 2), "Delete" button
The bar is intentionally minimal so the artifact content is the hero.
**Content area:**
- Full-width, full remaining viewport height
- Rendered inside an `