---
name: api.internet.dev
description: A REST API serving users, organizations, credits, marketplace, inventory, documents, events, posts, and more over PostgreSQL. All endpoints return JSON.
---

# SKILL — api.internet.dev

A REST API serving users, organizations, credits, marketplace, inventory, documents, events, posts, and more over PostgreSQL. All endpoints return JSON.

## Base URL

```
https://api.internet.dev
```

## Authentication

Authenticated endpoints require an `x-api-key` header. Get a key by creating an account:

```bash
curl -X POST https://api.internet.dev/api/users/authenticate \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'
```

The response includes a `user.key` field. Use it in subsequent requests:

```bash
curl https://api.internet.dev/api/users/viewer \
  -H "x-api-key: YOUR_KEY"
```

## Response Format

Success responses return entity fields at the top level:

```json
{ "user": { "id": "...", "email": "..." }, "existing": true }
{ "data": { ... }, "success": true }
```

Error responses always use:

```json
{ "error": true, "message": "description." }
```

## Usage Metadata

Every response — success and error — includes a `_usage` field with token cost, remaining balance, and rate limit info. This is injected automatically. Authenticated requests also include `tokens.remaining`.

```json
{
  "data": { ... },
  "_usage": {
    "tokens": { "cost": 0, "remaining": 12500 },
    "rate_limit": { "limit": 120, "remaining": 87, "interval_ms": 60000 }
  }
}
```

The same data is set as response headers: `X-Tokens-Cost`, `X-Tokens-Remaining`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Interval-Ms`.

## Token System

Every endpoint has a token cost (currently all 0 for launch). When an endpoint costs tokens:

- **402 Payment Required** is returned if your balance is insufficient
- The response includes `tokens_required`, `tokens_balance`, and `payment_url`
- Check your balance: `GET /api/credits/balance`
- See all pricing: `GET /api/credits/pricing`
- Every response tells you your remaining balance via `_usage.tokens.remaining`

## Rate Limits

Rate limiting uses a fixed-window counter per category per key/IP. All endpoints in the same category share one counter. Limits scale with user tier — higher tiers get proportionally more throughput.

**Categories (base limits for UNVERIFIED users):**

| Category | Base Requests | Per | Applied To |
|----------|---------------|-----|------------|
| read | 60 | 60s | GET endpoints and POST reads |
| write | 30 | 60s | Create, update, delete operations |
| financial | 5 | 60s | Credit send, deduct, distribute |
| auth | 5 | 60s | Authentication, password reset, verification, checkout |
| unlimited | no limit | — | Webhooks |

**Tier multipliers (applied to base request count):**

| Tier | Level | Multiplier | Reads/min | Writes/min |
|------|-------|------------|-----------|------------|
| UNVERIFIED | 0 | 1x | 60 | 30 |
| VERIFIED | 10 | 6x | 360 | 180 |
| PAYING | 20 | 12x | 720 | 360 |
| GENERAL_CO_WORKING | 30 | 120x | 7,200 | 3,600 |
| PARTNER | 40 | 1,200x | 72,000 | 36,000 |
| ADMIN | 100 | unlimited | unlimited | unlimited |

Every response includes `_usage.rate_limit` with `limit`, `remaining`, and `interval_ms`, plus the headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Interval-Ms`.

When rate limited, a `429 Too Many Requests` is returned:

```json
{ "error": true, "message": "rate limit exceeded. try again later.", "retry_after_ms": 45000 }
```

---

## Endpoints

### Status

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/status | No | Health check |

### Users

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/users/authenticate | No | Login or register with email and password. Pass `source` to tag the user's account with your app domain and use your custom email templates |
| POST | /api/users/authenticate-agent | No | Create paid agent account with Stripe payment method |
| POST | /api/users/authenticate-apple-client | No | Apple OAuth sign-in |
| GET | /api/users | Admin (level 100) | List all users |
| GET | /api/users/list-by-source | Admin (level 100) | List users by signup source |
| POST | /api/users/get-by-id | Admin (level 100) | Get user by ID |
| POST | /api/users/delete | API key | Delete a user |
| POST | /api/users/update | API key | Update user profile |
| POST | /api/users/update-viewer-password | API key | Change password |
| POST | /api/users/update-viewer-username | API key | Change username |
| POST | /api/users/regenerate-key | No | Generate new API key |
| POST | /api/users/reset-password | No | Reset password via verification code. Pass `source` to use your organization's custom reset password email |
| POST | /api/users/verify | No | Verify email with code |
| POST | /api/users/verify-check-by-id | Admin (level 100) | Check if email is verified |
| POST | /api/users/verify-resend | API key | Resend verification email. Pass `source` to use your organization's custom verification email |
| POST | /api/users/sync-wallet | API key | Associate wallet address |
| POST | /api/users/desync-wallet | API key | Remove wallet address |

### Users — Public

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/users/public/get-by-id | No | Public user lookup by ID |
| POST | /api/users/public/get-by-username | No | Public user lookup by username |

### Users — Viewer

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/users/viewer | API key | Get authenticated user profile |
| POST | /api/users/viewer/generate-add-payment-method-url | API key | Stripe billing portal link |
| GET | /api/users/viewer/get-current-payment-method | API key | Current Stripe payment method |
| GET | /api/users/viewer/likes | Verified (level 10+) | Items liked by user |
| GET | /api/users/viewer/organizations | Verified (level 10+) | User's organizations |
| POST | /api/users/viewer/pay-provider-amount-cents | API key | Manual Stripe payment |

### Users — Office

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/users/office | Admin (level 100) | List office applications |
| POST | /api/users/office/apply | Verified (level 10+) | Apply for workspace access |
| POST | /api/users/office/delete | Admin (level 100) | Remove application |
| POST | /api/users/office/status | Verified (level 10+) | Check application status |
| POST | /api/users/office/update | Admin (level 100) | Update application |

### Users — Subscriptions

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/users/subscriptions | No | List all subscriptions |
| POST | /api/users/subscriptions/check | API key | Verify subscription is active |
| POST | /api/users/subscriptions/get-by-user-id | API key | Get subscription for a user |
| POST | /api/users/subscriptions/get-current-invoices | API key | Fetch Stripe invoices |
| POST | /api/users/subscriptions/unsubscribe | API key | Cancel subscription |

### Organizations

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/organizations | Verified (level 10+) | List all organizations |
| POST | /api/organizations/create | Verified (level 10+) | Create organization |
| POST | /api/organizations/delete | Org admin | Delete organization |
| POST | /api/organizations/get-by-id | Verified (level 10+) | Get organization by ID |
| POST | /api/organizations/update | Org admin | Update organization settings and custom email templates |
| POST | /api/organizations/toggle-public-access | Org admin | Toggle public visibility |
| POST | /api/organizations/membership | Verified (level 10+) | List memberships |
| POST | /api/organizations/membership/automatic | Verified (level 10+) | Auto-join by email domain |
| POST | /api/organizations/users | Org member | List users in organization |
| POST | /api/organizations/users/add | Org admin | Add user to organization |
| POST | /api/organizations/users/remove | Org member | Remove user |
| POST | /api/organizations/users/promote | Org admin | Promote user |
| POST | /api/organizations/users/demote | Org admin | Demote user |

### Organizations — Custom Email Templates

When users sign up or reset their password through your app, the API sends them an email. By default these emails come from `API.INTERNET.DEV <no-reply@mail.internet.dev>`. If you run your own website through an organization, you can customize the sender, subject, and body for each email type so your users see your branding instead.

To connect a user action to your organization's email templates, pass your organization's `domain` as the `source` field in the relevant endpoint (`/api/users/authenticate`, `/api/users/verify-resend`, `/api/users/reset-password`). The API looks up your organization by that domain and uses your custom templates if they exist.

There are three email types you can customize through `POST /api/organizations/update`:

| Data Key | When It's Sent | Triggered By |
|----------|---------------|--------------|
| `email` | A new user needs to verify their email address | `/api/users/authenticate` (new account), `/api/users/verify-resend`, `/api/users/verify` (expired code) |
| `email_reset_password` | A user requests a password reset | `/api/users/reset-password` |
| `email_send_password` | A new user is created via OAuth and needs their generated password | OAuth sign-in (Google, Apple, Bluesky) |

Each email object has three fields:

| Field | Description | Example |
|-------|-------------|---------|
| `from` | Sender name and address | `"YourApp <no-reply@yourapp.com>"` |
| `subject` | Subject line | `"Verify your email for YourApp"` |
| `text` | Body text (the verification link or password is appended automatically) | `"Thanks for signing up! Click the link below to verify your email."` |

### Credits

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/credits | API key | List credit transactions |
| GET | /api/credits/balance | API key | Check token balance |
| GET | /api/credits/pricing | No | View all route costs and limits |
| POST | /api/credits/check | API key | Verify account ownership |
| POST | /api/credits/create-account-by-user-id | Admin (level 100) | Initialize credit account |
| POST | /api/credits/deduct | API key | Subtract credits |
| POST | /api/credits/distribute-bonus | API key | Award bonus credits |
| POST | /api/credits/distribute-to-user-id | Admin (level 100) | Monthly credit distribution |
| POST | /api/credits/get-balance-by-email | Admin (level 100) | Balance lookup by email |
| POST | /api/credits/get-balance-by-user-id | Admin (level 100) | Balance lookup by user ID |
| POST | /api/credits/send | API key | Transfer tokens to another user by email or username |
| POST | /api/credits/vendor-distribution-to-email | Admin (level 100) | Third-party credit distribution |
| POST | /api/credits/verify-apple-transaction-distritbution | API key | Verify Apple IAP and distribute |

### Posts

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/posts | No | List posts (body: `type`, plus `organization_id`/`user_id`/`username`/`email`) |
| GET | /api/posts/[id] | No | Get post by ID (private TXT_DEV/DIAGRAM_PAGE posts require API key + ownership) |
| POST | /api/posts/create | Verified (level 10+) | Create a post with `type` and `fields`. Optional `markdown` field parses markdown into Slate editor nodes |
| POST | /api/posts/delete | API key | Delete a post (owner or admin) |
| POST | /api/posts/update | Verified (level 10+) | Update a post's `data`, `slug`, or `src` (owner or admin). Optional `markdown` in updates parses markdown into Slate editor nodes |
| POST | /api/posts/admin-stats | Admin (level 100) | Post analytics |
| POST | /api/posts/all-threads | No | All discussion threads |
| POST | /api/posts/all-thread-replies | No | All thread replies |
| GET | /api/posts/public/[slug] | No | Public post by slug |
| POST | /api/posts/public/organizations/[id] | No | Public posts for organization |

### Posts — Creating and Publishing

Posts store content in a flexible JSONB `data` field. You can put any content you want in it, including a `body` field with plain text or markdown content.

**Creating a post** requires `type` and `fields`:

- `type` — A string identifying the post type. Use `GENERAL` for general-purpose posts, `TXT_DEV` for txt.dev posts, or any custom string.
- `fields` — An object that becomes the post's `data`. Common fields: `title`, `body`, `public`, `description`.
- `domain` — Optional. Your organization's domain if creating under an org (requires org membership).
- `src` — Optional. A unique source identifier.

The API also initializes an `editorContent` field with an empty Slate editor state. If you're creating posts from an agent or script (not the txt.dev editor), you can ignore `editorContent` and use `body` for your content instead.

**Publishing a post** is a two-step process:

1. Create the post (it starts as private by default).
2. Update it with `data.public: true` and a `slug` for the public URL.

For `TXT_DEV` and `DIAGRAM_PAGE` post types, only posts with `data.public: true` are visible in public listings. Other post types are always visible when listed. The `slug` must be unique across all posts.

**Updating a post** deep-merges `updates.data` into the existing post data, so you only need to send the fields you want to change. `slug` and `src` are replaced entirely if provided.

### Posts — Markdown Support

Both the create and update endpoints accept an optional `markdown` field. When provided, the API parses the markdown into Slate editor nodes so the post renders with full formatting in the txt.dev editor.

**Supported markdown:**
- Headings: `#` (heading 1), `##` and beyond (heading 2)
- Bold: `**text**` or `__text__`
- Italic: `*text*` or `_text_`
- Inline code: `` `code` ``
- Block quotes: `> text`
- Bulleted lists: `- `, `* `, `+ `
- Numbered lists: `1. `, `2. `
- Images: `![alt](url)` (whole line becomes an image block)
- Links: `[text](url)`

The raw markdown is also stored in `data.body` for reference. If both `markdown` and `fields.body` are provided, the markdown value takes precedence.

### Documents

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/documents | Org member | List documents (body: `type`, `domain`) |
| GET | /api/documents/[id] | No | Get document by ID |
| POST | /api/documents/create | Org member | Create document |
| POST | /api/documents/delete | Org member | Delete document |
| POST | /api/documents/update | Org member | Update document |

### Events

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/events | No | List events (requires `domain` query param; public users see approved public events only) |
| GET | /api/events/[id] | Verified (level 10+) | Get event by ID |
| POST | /api/events/create | Org member | Create event |
| POST | /api/events/delete | Org member | Delete event |
| POST | /api/events/update | Org member | Update event |
| POST | /api/events/conflicts | Org member | Check scheduling conflicts |

### Likes

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/likes/[id] | No | Get like record |
| POST | /api/likes/create | Verified (level 10+) | Like an item |
| POST | /api/likes/delete | API key | Remove a like |
| POST | /api/likes/update | API key | Update a like |

### Data

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/data | Verified (level 10+) | List data objects |
| POST | /api/data/delete | API key | Delete data object |
| POST | /api/data/generate-presigned-url | Org member | AWS S3 upload URL (15MB max) |
| POST | /api/data/generate-presigned-url-gcs | Org member | Google Cloud Storage upload URL |
| GET | /api/data/localizations/[id] | No | Get localization by ID |
| POST | /api/data/localizations/create | Verified (level 10+) | Create localization |
| POST | /api/data/localizations/delete | API key | Delete localization |
| POST | /api/data/localizations/update | API key | Update localization |
| POST | /api/data/localizations/get-by-entity-id | No | Localizations for entity |
| POST | /api/data/prices/by-label | No | Price lookup by label |
| GET | /api/data/things | No | Get thing by ID |
| POST | /api/data/things/create | Verified (level 10+) | Create thing |
| POST | /api/data/things/delete | API key | Delete thing |
| POST | /api/data/things/update | API key | Update thing |
| GET | /api/data/things/list | No | List things |
| GET | /api/data/things/list/search | No | Search things |

### Inventory

Universal inventory primitive for physical goods, gacha/loot systems, cash shops, virtual world items, event tickets, collectibles, and more. Stock is tracked via individual units (`inventory_units`) — each with its own JSONB `data` for serial numbers, rarity, coordinates, license keys, or any context-specific metadata. Each listing returns `available_count`. Items can be `private` (org-members only). Orgs can gate their storefront via `organization.data.inventory_private`.

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/inventory | No (private: members) | List items with `available_count` |
| GET | /api/inventory/[id] | No (private: members) | Get item with `available_count` and `units` array |
| POST | /api/inventory/claim | Verified (level 10+) + tier gate | Claim 1 available unit for free |
| POST | /api/inventory/create | Org admin | Create item. `unit_count` or `units` array creates initial units |
| POST | /api/inventory/delete | Org admin | Soft-delete item and all associated units |
| POST | /api/inventory/update | Org admin | Update item. Set `private: true` to hide from non-members |
| POST | /api/inventory/search | No (private: members) | Search with `domain`, `search`, `filters`, `priceRange` |
| POST | /api/inventory/units/add | Org admin | Add units to an item (`count` or `units` array) |
| POST | /api/inventory/units/update | Org admin | Update unit `data` or `status` |
| POST | /api/inventory/units/remove | Org admin | Soft-delete a unit |

### Marketplace

Handles cart, checkout (Stripe USD or tokens), orders, and discount codes. Token checkout enables instant in-app purchases for cash shops and gacha systems. Stripe checkout handles real-money purchases with shipping for physical goods.

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | /api/marketplace/cart | Verified (level 10+) | Get cart contents |
| POST | /api/marketplace/cart/add | Verified (level 10+) | Add item to cart |
| POST | /api/marketplace/cart/remove | Verified (level 10+) | Remove item from cart |
| POST | /api/marketplace/cart/clear | Verified (level 10+) | Clear cart |
| POST | /api/marketplace/cart/update | Verified (level 10+) | Update cart item |
| POST | /api/marketplace/checkout | Verified (level 10+) | Process payment via Stripe or tokens (payment_type) |
| GET | /api/marketplace/orders | Verified (level 10+) | List orders |
| GET | /api/marketplace/orders/[id] | Verified (level 10+) | Get order by ID |
| POST | /api/marketplace/orders/update | Verified (level 10+) | Update order status |
| GET | /api/marketplace/discounts | Org admin | List discount codes |
| GET | /api/marketplace/discounts/[id] | Org admin | Get discount by ID |
| POST | /api/marketplace/discounts/create | Org admin | Create discount code |
| POST | /api/marketplace/discounts/delete | Org admin | Delete discount code |
| POST | /api/marketplace/discounts/update | Org admin | Update discount code |

### Marketplace — Guest Checkout

Guest checkout lets customers purchase with just a credit card — no account, email, or name required. Organizations opt in via `guest_checkout_enabled: true` in their org data. Guest sessions use a short-lived JWT (24h). See `public/skills/marketplace-guest/SKILL.md` for the full guide.

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/marketplace/guest/session | No | Create guest session JWT (requires `domain`) |
| GET | /api/marketplace/guest/cart | Guest session | View guest cart |
| POST | /api/marketplace/guest/cart/add | Guest session | Add item to guest cart |
| POST | /api/marketplace/guest/cart/remove | Guest session | Remove item from guest cart |
| POST | /api/marketplace/guest/cart/update | Guest session | Update guest cart item quantity |
| POST | /api/marketplace/guest/cart/clear | Guest session | Clear guest cart |
| POST | /api/marketplace/guest/checkout | Guest session | Guest checkout via Stripe PaymentMethod |
| GET | /api/marketplace/guest/orders/[id] | No | Look up guest order by UUID (PII not returned) |
| GET | /api/marketplace/guest/orders/[id]/status | No | Lightweight guest order status |

### Webhooks

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/webhooks/stripe | No | Stripe webhook handler |

### Utility

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | /api/aes | No | AES encryption endpoint |
| POST | /api/404 | No | 404 handler |

---

## User Tiers

| Level | Tier | Monthly Credits |
|-------|------|-----------------|
| 0 | UNVERIFIED | 0 |
| 10 | VERIFIED | 0 |
| 20 | PAYING | 1,500 |
| 30 | GENERAL_CO_WORKING | 45,000 |
| 40 | PARTNER | 45,000 |
| 100 | ADMIN | 45,000 |

## Examples

### Create account and authenticate

```bash
curl -X POST https://api.internet.dev/api/users/authenticate \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'
```

### Get authenticated user profile

```bash
curl https://api.internet.dev/api/users/viewer \
  -H "x-api-key: YOUR_KEY"
```

### Check token balance

```bash
curl https://api.internet.dev/api/credits/balance \
  -H "x-api-key: YOUR_KEY"
```

### View pricing for all endpoints

```bash
curl https://api.internet.dev/api/credits/pricing
```

### Create a post

```bash
curl -X POST https://api.internet.dev/api/posts/create \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "type": "GENERAL",
    "fields": {
      "title": "My First Post",
      "body": "Hello world"
    }
  }'
```

### Create a post with markdown (rendered in editor)

When you pass a `markdown` field, the API parses it into Slate editor nodes. The post will render with full formatting in the txt.dev editor.

```bash
curl -X POST https://api.internet.dev/api/posts/create \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "type": "TXT_DEV",
    "fields": {
      "title": "Getting Started Guide",
      "description": "A quick guide to getting started"
    },
    "markdown": "# Getting Started\n\nWelcome to the platform.\n\n## Features\n\n- **Authentication** with OAuth\n- **Organizations** for teams\n- *Inline code* and `snippets`\n\nFor more details, visit [our docs](https://api.internet.dev)."
  }'
```

### Create a post with raw markdown in body (not parsed)

If you want to store markdown as plain text without Slate parsing, put it in `fields.body` instead:

```bash
curl -X POST https://api.internet.dev/api/posts/create \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "type": "GENERAL",
    "fields": {
      "title": "Getting Started Guide",
      "body": "# Getting Started\n\nWelcome to the platform.",
      "description": "A quick guide to getting started with the API"
    }
  }'
```

### Publish a post

Publishing makes a post publicly accessible. Set `public` to `true` and provide a `slug` for the public URL.

```bash
curl -X POST https://api.internet.dev/api/posts/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "id": "POST_ID",
    "updates": {
      "slug": "getting-started-guide",
      "data": { "public": true }
    }
  }'
```

The post is now accessible at `/api/posts/public/getting-started-guide`.

### Update a post

Updates are deep-merged into the existing post data, so you only need to send the fields you want to change.

```bash
curl -X POST https://api.internet.dev/api/posts/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "id": "POST_ID",
    "updates": {
      "data": {
        "title": "Updated Title",
        "body": "Updated content with **markdown** support."
      }
    }
  }'
```

### Update a post's content with markdown (rendered in editor)

```bash
curl -X POST https://api.internet.dev/api/posts/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "id": "POST_ID",
    "updates": {
      "markdown": "# Updated Content\n\nThis replaces the editor content with **formatted** text.\n\n- First point\n- Second point\n\n> A blockquote for emphasis."
    }
  }'
```

### List your posts

```bash
curl -X POST https://api.internet.dev/api/posts \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"type": "GENERAL", "user_id": "YOUR_USER_ID"}'
```

### List a user's public posts by username

```bash
curl -X POST https://api.internet.dev/api/posts \
  -H "Content-Type: application/json" \
  -d '{"type": "TXT_DEV", "username": "someone"}'
```

### Get a public post by slug

```bash
curl https://api.internet.dev/api/posts/public/getting-started-guide
```

### Delete a post

```bash
curl -X POST https://api.internet.dev/api/posts/delete \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"id": "POST_ID"}'
```

### Send tokens to another user

```bash
# By username (preferred for agents)
curl -X POST https://api.internet.dev/api/credits/send \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"username": "recipient", "amount": 500}'

# By email (preferred for humans)
curl -X POST https://api.internet.dev/api/credits/send \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"email": "recipient@example.com", "amount": 500}'
```

### Checkout with tokens

```bash
curl -X POST https://api.internet.dev/api/marketplace/checkout \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"items": [{"sku": "ITEM-001", "quantity": 1}], "payment_type": "tokens", "domain": "your-org.com"}'
```

### Claim a tier-gated item

```bash
curl -X POST https://api.internet.dev/api/inventory/claim \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"sku": "ITEM-001", "domain": "your-org.com"}'
```

### Customize your organization's verification email

```bash
curl -X POST https://api.internet.dev/api/organizations/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "domain": "your-org.com",
    "data": {
      "email": {
        "from": "YourApp <no-reply@yourapp.com>",
        "subject": "Verify your email for YourApp",
        "text": "Thanks for signing up! Click the link below to verify your email."
      }
    }
  }'
```

### Customize your organization's reset password email

```bash
curl -X POST https://api.internet.dev/api/organizations/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "domain": "your-org.com",
    "data": {
      "email_reset_password": {
        "from": "YourApp <no-reply@yourapp.com>",
        "subject": "Reset your YourApp password",
        "text": "We received a request to reset your password. Click the link below to sign in and change it."
      }
    }
  }'
```

### Customize your organization's send password email

```bash
curl -X POST https://api.internet.dev/api/organizations/update \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{
    "domain": "your-org.com",
    "data": {
      "email_send_password": {
        "from": "YourApp <no-reply@yourapp.com>",
        "subject": "Your new YourApp account",
        "text": "Welcome! Here is your password. Please sign in and change it as soon as possible."
      }
    }
  }'
```

### Authenticate a user with your organization's custom emails

```bash
curl -X POST https://api.internet.dev/api/users/authenticate \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "their-password", "source": "your-org.com"}'
```

### Reset a user's password with your organization's custom email

```bash
curl -X POST https://api.internet.dev/api/users/reset-password \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "source": "your-org.com"}'
```

### Guest checkout — create session and buy a physical item

```bash
# 1. Create a guest session
curl -X POST https://api.internet.dev/api/marketplace/guest/session \
  -H "Content-Type: application/json" \
  -d '{"domain": "your-org.com"}'

# 2. Checkout with payment method and shipping address (no email needed for physical goods)
curl -X POST https://api.internet.dev/api/marketplace/guest/checkout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SESSION_TOKEN" \
  -d '{
    "items": [{"sku": "TSHIRT-L-BLK", "quantity": 1}],
    "payment_method_id": "pm_1234567890",
    "shipping_address": {"street": "123 Main St", "city": "Portland", "state": "OR", "zip": "97201", "country": "US"}
  }'

# 3. Look up the order by ID (no auth needed)
curl https://api.internet.dev/api/marketplace/guest/orders/ORDER_ID
```

### Health check

```bash
curl https://api.internet.dev/api/status
```
