# Pikes API Reference

Base URL: `https://pikes.ai`

There are two ways into Pikes programmatically, and they run the same tools over the same account:

| | Use it for | Auth |
|---|---|---|
| **MCP server** — `POST /mcp` | Agent clients (Claude, ChatGPT connectors, anything speaking MCP) | OAuth 2.1, or an API key as a bearer token |
| **REST tool calls** — `POST /mcp/tools/:toolName` | Your own orchestration layer; no MCP client needed | API key |
| **Product-shot endpoints** — `POST /api/product-shot` | One-shot product photography from a file upload | API key |

Every call runs as a Pikes **user** and spends that user's credits. There is no team- or org-level credential today; see [Workspace scoping](#workspace-scoping) before you plan around it.

- MCP protocol: **2025-06-18** (also accepts `2024-11-05`)
- Transports: `streamable_http`, `http_sse`
- OAuth scopes: `generate_image`, `edit_image`, `expand_image`, `read_images`
- Published at [https://pikes.ai/docs/api](https://pikes.ai/docs/api), linked from Settings → API and Settings → Claude / MCP
- Verified against `https://pikes.ai` on 2026-08-25

These facts and the tool catalogue below are written by `server/__tests__/api-surface-probe.js` from a live server — see [Verifying this document](#verifying-this-document).

---

## Authentication

### OAuth 2.1 (MCP clients)

Standard discovery — point a compliant MCP client at `https://pikes.ai/mcp` and it will find everything itself:

- `GET /.well-known/oauth-authorization-server`
- `GET /.well-known/oauth-protected-resource`

| Endpoint | Purpose |
|---|---|
| `POST /mcp/oauth/register` | Dynamic client registration (RFC 7591) |
| `POST /mcp/oauth/authorize` | Authorization code |
| `POST /mcp/oauth/token` | Token exchange / refresh |
| `POST /mcp/oauth/revoke` | Revocation |

PKCE is required (`S256`). Grants: `authorization_code`, `refresh_token`. Scopes: `generate_image`, `edit_image`, `expand_image`, `read_images`.

### API keys (headless)

Keys look like `psk_…`, are stored only as a SHA-256 hash, and are accepted two ways:

```
Authorization: Bearer psk_…
x-api-key: psk_…
```

Create one in the app under **Settings → API** (or **Settings → Claude / MCP** for a key you'll paste into an MCP client), or over the API. Key management is authenticated by the **user's session JWT**, never by a key — a key cannot mint or revoke keys.

```bash
# Create
curl -X POST https://pikes.ai/api/keys \
  -H "Authorization: Bearer <SUPABASE_SESSION_JWT>" \
  -H "Content-Type: application/json" \
  -d '{"name": "orchestrator"}'

# → { "success": true, "key": { "id": "...", "name": "orchestrator",
#     "keyPrefix": "psk_754fed7b...", "rawKey": "psk_…", "createdAt": "..." } }
```

`rawKey` is returned **once**. Store it then; it cannot be read back.

```bash
curl https://pikes.ai/api/keys -H "Authorization: Bearer <JWT>"          # list (metadata only)
curl -X DELETE https://pikes.ai/api/keys/<keyId> -H "Authorization: Bearer <JWT>"   # revoke
```

Listing returns `id`, `name`, `key_prefix`, `created_at`, `last_used_at`, `revoked` — never key material. Revoking a key you don't own returns `404`.

**Scope difference worth knowing:** API-key calls carry `generate_image`, `edit_image`, `expand_image`. OAuth additionally carries `read_images`.

---

## REST tool calls

`POST /mcp/tools/:toolName` — the whole tool catalogue as ordinary HTTP. The body is the tool's arguments; no JSON-RPC envelope.

```bash
curl -X POST https://pikes.ai/mcp/tools/generate_image \
  -H "x-api-key: psk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Amber supplement bottle on warm marble, soft window light",
    "aspectRatio": "4:5",
    "model": "consistency_pro",
    "resolution": "2K"
  }'
```

Responses come back in MCP content-block form. The payload is JSON encoded inside `text`:

```json
{ "content": [ { "type": "text", "text": "{\"success\":true,\"images\":[…]}" } ] }
```

Tool-level failures return HTTP `200` with `"isError": true` and the reason in the same block — check `isError`, not just the status code:

```json
{ "content": [ { "type": "text", "text": "{\"success\":false,\"error\":\"Could not fetch account info\"}" } ], "isError": true }
```

Missing or bad credentials return `401`:

```json
{ "error": "Unauthorized",
  "message": "OAuth token or API key required. Authorize at /mcp/oauth/authorize or use x-api-key header" }
```

---

## MCP transport

- `POST /mcp` — Streamable HTTP, JSON-RPC 2.0 (`initialize`, `tools/list`, `tools/call`, `resources/*`, `prompts/*`). Protocol `2025-06-18`, with `2024-11-05` still accepted.
- `POST /mcp/sse` + `POST /mcp/message` — the deprecated SSE transport, kept for older clients.
- `GET /mcp` — server info: transports, auth URLs, and the live tool list.

Discovery is open on purpose: `initialize` and `tools/list` answer without credentials, so a client can inspect the catalogue before anyone authorizes it. Executing a tool always needs one.

```bash
curl -X POST https://pikes.ai/mcp \
  -H "Authorization: Bearer psk_…" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Claude Code, for reference:

```bash
claude mcp add --transport http pikes-ai https://pikes.ai/mcp
```

---

## Tool catalogue

29 tools, as reported by `tools/list`.

**Image and video**

| Tool | Description |
|---|---|
| `generate_image` | Generate a new image from scratch using a text prompt. Use this when the user wants to create a completely new image without any input images. |
| `edit_image` | Edit or modify existing images based on instructions. Supports single image editing AND multi-image combining (e.g. placing a product into a scene). For single image: pass imageUrl. For combining images: pass imageUrls — first image is the product/main subject, last image is the scene/context, middle images are style references. Provide exactly one of imageUrl or imageUrls. This replaces the old remix_images tool. |
| `expand_image` | Extend or outpaint an image's canvas to a new aspect ratio or size. The expansion is content-aware but NOT steerable: the underlying model (Ideogram Reframe) fills the new canvas from the source image alone — there is no directional control and no prompt guidance. Use edit_image if the user wants specific new content added around the image. |
| `animate_image` | Animate a still image into a short video. Picks between several models via the `model` param: |
| `remove_background` | Remove the background from a product image, returning a transparent PNG. Use this when the user wants to isolate a product, cut out the background, or get a transparent version of an image. |
| `upload_image` | Upload an image to get a publicly accessible URL for use with edit_image, expand_image, etc. |

**Async jobs**

| Tool | Description |
|---|---|
| `submit_image_job` | Start an image/video generation ASYNCHRONOUSLY and return a jobId immediately (does NOT wait for the render). Use this instead of generate_image/edit_image when you want to avoid long waits/timeouts on 4K or video, run several jobs in parallel, or build a pipeline. After submitting, poll get_image_job with the returned jobId until status is "completed", then use the result URLs (e.g. feed one into another submit_image_job with tool="edit_image"). For tool="animate_image", pick the video model via videoModel (NOT model) and use duration/resolution/endImageUrl/generateAudio/videoUrls/audioUrls as with the sync animate_image tool. Credits are charged once, when the job runs. |
| `get_image_job` | Retrieve a job submitted with submit_image_job. Returns status ("processing" \| "completed" \| "failed" \| "timed_out"), progress, and — when completed — the result images/videos with downloadable URLs. Poll until status is "completed" (or "failed"/"timed_out"). On "completed", results[] contains { assetId, url, width, height, aspectRatio, assetType }. |

**Products, brand and account**

| Tool | Description |
|---|---|
| `get_product_images` | Search the user's saved product library and return image URLs. Call this tool IMMEDIATELY — without asking the user first — whenever they mention a product by name or reference (e.g. "the t-shirt", "my sneakers", "the coffee bag", "possible t shirt", "that product"). Do NOT ask the user to upload an image or confirm — just call this tool. The returned URLs can be passed directly to edit_image. |
| `fetch_products_from_website` | Fetch product images directly from a brand's website. Use this when the user gives a website URL or domain, or asks to grab/import/pull products FROM a site (e.g. "grab the products from acme.com"). Do NOT use it for products already in the user's library — use get_product_images for those; if get_product_images finds nothing and the user has mentioned their website, call this. Works only on Shopify-powered stores (most DTC brands are); non-Shopify sites return a no-products error. By default it also saves the found products into the user's product library; set save_to_library to false only if the user explicitly wants a look without importing. Returns product titles and image URLs you can pass straight to edit_image. |
| `get_all_context` | Retrieve all context files (brand knowledge, guidelines, campaign rules, etc.) with full content. Use when the injected context was truncated or you need inactive files. Context files are the user's persistent brand knowledge base. |
| `update_context` | Update or create a context file. Context files store persistent brand knowledge (voice, memory, colors, campaign rules, audience info, etc.). If a file with this name exists, its content is replaced. If not, a new file is created. When updating, rewrite the full content with your changes incorporated. |
| `get_account_info` | Get the user's account information including credits remaining, subscription plan, and usage. Use when the user asks about their account, credits, or usage. |

**Boards**

| Tool | Description |
|---|---|
| `list_boards` | List the user's image boards. Use this BEFORE add_images_to_board or update_board to find an existing board's id, or to confirm a board with that name doesn't already exist before calling create_board. Includes personal boards and any team boards the user has access to. Returns: id, name, description, is_public, team_ids, image_count. |
| `get_board_images` | Read the images on a board and return their downloadable URLs + metadata. Identify the board by boardId (preferred — use list_boards to find it) or by name (returns the most recently updated board you can access with that name). Returns each image's url, videoUrl (for videos), width/height/aspectRatio, assetId, name (display name), model, source, origin, prompt, assetType, and position, plus the board's namingTemplate. `origin` is "upload" (the user put it on the board) or "generated" (Pikes made it) — boards mix both, since generated results are saved back onto the board holding the references. When sourcing a product shot or style reference to feed into edit_image / submit_image_job, filter to origin "upload": reusing a generated image as a source compounds its distortions. `prompt` is the generation prompt that created the image (empty for uploads) — to recreate a similar shot, start from that prompt and adapt it. |
| `create_board` | Create a new image board. Returns the board id — pass it to add_images_to_board next. Visibility defaults to "private"; pass "team" + teamIds to share with one or more teams, or "public" to make the board world-readable. Public boards return share_url (https://pikes.ai/b/<id>) — when sharing a link, use that exact URL; never construct board URLs yourself. |
| `update_board` | Edit an existing board's name, description, or visibility. Only the board owner can update. To change visibility, pass the new "visibility" value (and teamIds when switching to "team"). Pass null for fields to leave unchanged. Boards cannot be deleted via MCP. Public boards return share_url (https://pikes.ai/b/<id>) — when sharing a link, use that exact URL; never construct board URLs yourself. |
| `add_images_to_board` | Add one or more images to a board. Pass assetId(s) from generate_image / edit_image / expand_image ("id" field), OR image URL(s) via publicUrl/publicUrls — Pikes URLs are linked directly, and EXTERNAL http(s) image URLs (e.g. find_ads ad stills, web images) are automatically fetched and re-hosted as the user's assets. Do not run a dummy edit_image just to get an id. The user must be the board owner or a team member with write access. |
| `batch_rename_assets` | Rename assets on a board (sets each asset's display name — storage URLs never change). Flow: get_board_images first (returns name/model/source/prompt per image), derive a short concept name per image from its prompt/content, then ALWAYS show the user the proposed old → new rename table and wait for confirmation BEFORE calling this tool. Names: lowercase, hyphens inside, underscores between fields, no spaces. This renames the global asset name (visible everywhere); for per-download filename conventions set the board's namingTemplate via update_board instead. |
| `list_teams` | List the teams the user is a member of. Use this when the user asks to share a board with a team — you need the team id to pass to create_board / update_board with visibility="team". |

**Research**

| Tool | Description |
|---|---|
| `find_ads` | Search Foreplay's ad library for real ads from any brand/competitor — for INSPIRATION and competitive research, NOT as generation inputs. Signature use: find a competitor's (or a niche's) ads that have run a LONG time, which signals the creative is likely working ("winning ads") — set sort:"longest_running", live:true, minRunningDays (30=working, 60/90=proven). Also browse by keyword/theme, niche, format, or platform. Returns ad image stills + metadata (brand, days running, headline, CTA). Analyze their composition, then create with the user's OWN product — do NOT pass the returned ad URLs into edit_image as the product. |

**Meta Ads and everything else**

| Tool | Description |
|---|---|
| `meta_get_ad_accounts` | List all Meta ad accounts the user has access to. Returns account ID, name, currency, and status. Call this first to get the accountId needed for other Meta tools. |
| `meta_get_ad_comments` | Read user comments on a Meta ad — Facebook AND Instagram. Resolves the ad to its underlying post automatically (works on dark/unpublished posts too), so pass an adId from meta_get_ads, NOT a post id. |
| `meta_get_ad_creative` | Get just the creative assets for a specific ad by ID. Use this when you already know the ad_id and only need the creative (not metrics). |
| `meta_get_ad_insights` | Get detailed performance insights with conversions, ROAS, and breakdowns. |
| `meta_get_ads` | Get ads with creative assets, performance metrics, conversions, and ROAS in a single call. |
| `meta_get_audience_breakdowns` | Get available audience and delivery segment rows for specific ads. |
| `meta_get_campaigns` | List campaigns for an ad account. Returns campaign ID, name, objective, status, budget, and date range. |
| `meta_get_page_comments` | Read user comments on a Facebook Page's ORGANIC posts (posts that were never run as ads). Use alongside meta_get_ad_comments for full voice-of-customer coverage. Pass a pageId (e.g. the pageId returned by meta_get_ad_comments, or the part before the underscore in an effective_object_story_id). Returns comments tagged source:"organic". Requires that the user administers the page. |

`GET /mcp` returns the same list with full schemas.

### Common parameters

| Parameter | Values |
|---|---|
| `aspectRatio` | `auto`, `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16` (default `9:16`) |
| `resolution` | `1K`, `2K`, `4K` — for `consistency_pro` these map to quality `low` / `medium` / `high` |
| `model` | `creativity_pro` (Google Nano Banana Pro, default) and `consistency_pro` (OpenAI GPT Image 2 — stronger text and label fidelity) are the two to reach for. Also accepted: `creativity_fast`, `creativity_light`, `flux_krea`, `ideogram_v3`, `ideogram_v4`, `google_edit`, `google_edit_pro`, `gpt_image_2_edit` |
| `imageCount` | 1–4 (default 1) |
| `imageUrls` | up to 9 reference images on `edit_image` |

Product identity is reference-conditioned, not fine-tuned: pass the actual pack shots as `imageUrls` (or let `get_product_images` fetch them from the product library) and the model works from those pixels. New packaging = new reference images, no retraining step.

### Long renders

4K and video can outrun an HTTP timeout. Submit and poll instead:

```bash
curl -X POST https://pikes.ai/mcp/tools/submit_image_job \
  -H "x-api-key: psk_…" -H "Content-Type: application/json" \
  -d '{"tool":"generate_image","prompt":"…","resolution":"4K"}'
# → jobId

curl -X POST https://pikes.ai/mcp/tools/get_image_job \
  -H "x-api-key: psk_…" -H "Content-Type: application/json" \
  -d '{"jobId":"…"}'
```

Credits are charged once, when the job runs. Jobs are scoped to the caller.

---

## Product-shot endpoints

Two multipart endpoints for the simplest case — send a file, get a finished shot.

```bash
curl -X POST https://pikes.ai/api/product-shot \
  -H "x-api-key: psk_…" \
  -F "image=@bottle.jpg" \
  -F "aspect_ratio=4:5" \
  -F "resolution=4K"
```

| | `/api/product-shot` | `/api/product-shot-custom` |
|---|---|---|
| Prompt | Fixed studio product-photography prompt | Your `prompt` field (required) |
| `aspect_ratio` | `auto` (default) or any ratio above | same |
| `resolution` | `1K` (default) or `4K` | same |

`1K` runs Nano Banana; `4K` runs Nano Banana Pro. Success:

```json
{ "success": true, "image": { "url": "…" }, "creditsUsed": 12 }
```

Cost today: **6 credits** at `1K`, **12 credits** at `4K`. Rate limit: **30 requests per minute per key**. Credits are pre-charged and refunded automatically if the render fails.

---

## Errors

| Status | Meaning |
|---|---|
| `400` | Validation — missing prompt, bad `aspect_ratio`, unknown `resolution`, `imageCount` out of range |
| `401` | No/invalid/revoked credential |
| `402` | Insufficient credits, or no active subscription — body carries the shortfall detail |
| `404` | Not found, or not yours (revoking someone else's key looks like this on purpose) |
| `429` | Rate limited (product-shot endpoints) |
| `500` | Render or server failure — pre-charged credits are refunded |

MCP tool calls that fail for content or upstream reasons return `200` with `isError: true` rather than an HTTP error.

Only the product-shot endpoints are rate limited today. On `/mcp` and `/mcp/tools/*` the practical ceiling is the account's credit balance — build your own backoff if you fan out hard.

---

## Workspace scoping

A credential identifies **one user**, and calls execute in that user's personal workspace: assets created over the API land with no team attached, whatever teams the user belongs to. `list_teams` exists so a board can be shared with a team after the fact, but there is no way to say "run this call as the ACME workspace."

Practical consequences for a multi-seat evaluation:

- Two users running the same brief will not see each other's product library or Context unless those were created in a shared team through the app.
- Assets generated headlessly won't appear in a team workspace by themselves.

If a connection that belongs to a team rather than a person is a requirement, treat it as a roadmap item, not a configuration flag.

---

## Verifying this document

`server/__tests__/api-surface-probe.js` exercises this surface end to end and rewrites the generated blocks above from what the server answered, so the doc cannot quietly drift from the API.

```bash
# against a local server (PORT=5099 node -r ./__tests__/boot-stub.js index.js)
node server/__tests__/api-surface-probe.js

# against production, no account needed — discovery, transport, auth negatives
node server/__tests__/api-surface-probe.js --base https://pikes.ai --quick

# refresh the generated blocks in this file
node server/__tests__/api-surface-probe.js --write-docs

# include one real 1K render (~6 credits)
node server/__tests__/api-surface-probe.js --generate
```

It covers discovery and PKCE advertisement, the auth negatives (no credential, bogus key, minting a key without a session), the key lifecycle across two accounts including cross-account revoke, `initialize` and `tools/list`, a REST tool call and its content-block envelope, revocation actually killing a key, and whether every live tool is documented here. A full run creates two throwaway users and deletes them; `--quick` creates nothing. Exit code is non-zero on any failure, so it drops straight into a loop or a scheduled check.

---

## Data and billing notes

- Generated assets are stored in the caller's Pikes library and are retrievable through the board and asset tools.
- Credits are charged per generation, by model and resolution; `creditsUsed` is returned where a call charges directly. A failed render is refunded.
- Data governance, incident response, and third-party risk policies live in `docs/policies/`.
