# The Spawn API Reference

Base URL: `https://thespawn.io`

## Chat

### POST /api/chat

Stream a chat completion with an agent. Returns Server-Sent Events (SSE) with plain text tokens, compatible with Vercel AI SDK's `TextStreamChatTransport`.

**Headers**:
- `Content-Type: application/json`

**Request Body**:

```json
{
    "agent_id": 42,
    "messages": [
        {
            "role": "user",
            "content": "What can this agent do for me?"
        }
    ]
}
```

The endpoint accepts two message formats:

1. **Simple format**: `{ "role": "user", "content": "text" }`
2. **Vercel AI SDK format**: `{ "role": "user", "parts": [{ "type": "text", "text": "text" }] }`

Both formats are normalized internally. Only text parts are extracted.

**Parameters**:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `agent_id` | integer | Yes | Database ID of the agent (from agents.id) |
| `messages` | array | Yes | Array of message objects (min 1) |
| `messages[].role` | string | Yes | One of: `user`, `assistant`, `system` |
| `messages[].content` | string | No* | Message text (simple format) |
| `messages[].parts` | array | No* | Message parts (Vercel AI SDK format) |

*Either `content` or `parts` must be provided.

**Response**: `200 OK` with `Content-Type: text/event-stream`

The response streams plain text tokens directly (not wrapped in `data:` SSE frames). This is compatible with Vercel AI SDK's `TextStreamChatTransport` using `streamProtocol: 'text'`.

**Response Headers**:
```
Content-Type: text/event-stream
Cache-Control: no-cache, no-store
Connection: keep-alive
X-Accel-Buffering: no
Set-Cookie: chat_session=<uuid>; Max-Age=2592000
```

**Tool Calling**: If the agent has callable HTTP endpoints (web, MCP, A2A, OASF), the backend automatically:
1. Generates OpenAI function tool definitions from agent.services
2. Sends tools alongside messages to the LLM
3. Executes tool calls by proxying HTTP to the agent's endpoints
4. Feeds results back to the LLM (up to 3 rounds)

The tool execution is transparent to the client: the response stream contains only the final text output.

**Rate Limiting**: 60 requests per session token per hour. Returns `429` when exceeded.

**Error Responses**:

- `422 Unprocessable Entity` - Validation error (missing agent_id, invalid messages)
```json
{
    "message": "The agent id field is required.",
    "errors": {
        "agent_id": ["The agent id field is required."]
    }
}
```

- `429 Too Many Requests` - Rate limit exceeded (plain text response)
```
Rate limit exceeded. Please try again later.
```

**Session Cookie**: A `chat_session` cookie (UUID, 30-day expiry) is set on the response. This cookie:
- Identifies the chat session for rate limiting
- Persists chat history (stored in chat_sessions table)
- Is created automatically if not present

## Favorites

Favorites require authentication via Google OAuth. Unauthenticated requests receive `401`.

### POST /api/favorites/toggle

Toggle favorite status for an agent.

**Headers**:
- `Content-Type: application/json`
- `X-CSRF-TOKEN: <token>` (from meta tag)
- `X-Requested-With: XMLHttpRequest`

**Request Body**:
```json
{
    "agent_id": 42
}
```

**Response**: `200 OK`
```json
{
    "favorited": true
}
```

Returns `favorited: true` if the agent was added to favorites, `false` if removed.

**Error Responses**:
- `401 Unauthorized` - Not authenticated
- `422 Unprocessable Entity` - Invalid agent_id

### GET /api/favorites

List all favorited agent IDs for the authenticated user.

**Response**: `200 OK`
```json
{
    "agent_ids": [42, 88, 155]
}
```

**Error Responses**:
- `401 Unauthorized` - Not authenticated

## Auth Routes

### GET /auth/google

Redirects to Google OAuth consent screen. Stores the referring page URL in the session for post-login redirect.

### GET /auth/google/callback

Handles the Google OAuth callback. Creates or updates the user record, logs them in with "remember me", and redirects to the page they were on before login.

### POST /logout

Logs out the current user, invalidates the session, regenerates the CSRF token, and redirects to `/`.

**Headers**:
- `X-CSRF-TOKEN: <token>`

## Page Routes

These are Inertia.js routes that return rendered HTML pages (not JSON APIs).

| Method | Path | Description |
|--------|------|-------------|
| GET | `/` | Homepage with stats, featured agents, categories |
| GET | `/agents` | Agent listing with search, filters, pagination |
| GET | `/agents/{chain}/{tokenId}` | Agent detail page with chat |
| GET | `/up` | Health check (Laravel default) |
| GET | `/sitemap.xml` | XML sitemap |

### Agent Listing Query Parameters

| Parameter | Type | Description | Example |
|-----------|------|-------------|---------|
| `search` | string | Search by name or description (LIKE) | `search=defi` |
| `protocol` | string | Filter by service protocol (JSONB match) | `protocol=MCP` |
| `chain` | string | Filter by chain slug | `chain=base` |
| `category` | string | Filter by category keywords | `category=defi` |
| `sort` | string | Sort order: `newest`, `score`, `name`, or empty (default) | `sort=score` |
| `favorites` | string | Show only favorites (requires auth) | `favorites=true` |
| `page` | integer | Pagination page number | `page=2` |

### Supported Chain Slugs

`ethereum`, `optimism`, `bsc`, `gnosis`, `solana`, `polygon`, `monad`, `xlayer`, `shape`, `metis`, `soneium`, `goat`, `abstract`, `megaeth`, `mantle`, `base`, `plasma`, `gatelayer`, `arbitrum`, `celo`, `avalanche`, `linea`, `taiko`, `scroll`, `skale`

### Agent Categories

`defi`, `analytics`, `security`, `trading`, `nft`, `social`, `infrastructure`, `ai-ml`

Categories are keyword-based: agents are categorized by matching words in their description against predefined keyword lists.

## Rate Limiting

| Endpoint | Limit | Window | Scope |
|----------|-------|--------|-------|
| POST /api/chat | 60 requests | 1 hour | Per session token (cookie) |
| API routes | Laravel default | Per minute | Per IP |

The chat rate limiter uses the `chat_session` cookie as the identifier. If no cookie is present, a new UUID is generated.

## CORS and Security

- API routes use `ForceJsonResponse` middleware, which sets `Accept: application/json` on all API requests. This ensures validation errors return JSON (422) instead of HTML redirects (302).
- Security headers are set via Nginx: CSP, X-Content-Type-Options (nosniff), Referrer-Policy (strict-origin-when-cross-origin), Permissions-Policy (no camera/mic/geo).
- All proxied requests (`trustProxies(at: '*')`) trust Traefik/Cloudflare headers for correct HTTPS detection.
