# Build on Fisshing v2

Base URL: https://fisshing.net
OpenAPI spec: /api/v2/openapi.json
Starter kit: /integrations/README.md (Node 20+, no packages)

## First request

GET /api/v2/catalog?q=salmon&limit=5
GET /api/v2/world

These routes need no key. Catalog filters: q, location, rarity, limit, offset.
World data includes the current time period, weather, featured fish, and player
counts. It contains no player IDs. Public responses can be cached briefly.

## Authentication

Find your API key in-game at Profile → API Key. Set FISSHING_API_KEY on your
computer or server. Send Authorization: Bearer KEY with authenticated requests.
Never put the key in public source code or a shared browser app.
New keys are random. Existing keys keep working after upgrade. To replace a
key, press Shift+R twice on the in-game API Key screen. This revokes the old
key, closes its overlay connections, and keeps your game session open.
Authenticated routes allow 20 requests/second per player; notifications allow
two/second. Rate-limited requests return 429 with Retry-After: 1.

## Player and agent tools

GET /api/v2/me/state — gold, location, rod, skills, catch and gold totals.
GET /api/v2/me/fish — your inventory, not the full fish catalog.
GET /api/v2/me/collection — your journal, best catches, and achievements.
These reads work online and from saved state when offline.

POST /api/v2/notify with {"message":"Tests passed","source":"build","alert_level":"success"}.
The message limit is 80 characters. The optional source limit is 20 characters.
POST /api/v2/status with {"active":true} or {"active":false}.
Both routes need an active game session. Clear activity when your task finishes,
including when the task fails. See /integrations/agent-task.mjs.

## Agent Desk and replies

Open Profile → Agent Desk in the game to see tasks and answer questions.
Claude Code sends AskUserQuestion prompts to the desk and passes your answers back through its plugin hook.
Codex uses native status hooks and plugin MCP question tools. It does not replace
the built-in Codex question prompt. OpenCode uses a native plugin and its versioned
question reply API. The plugins send task status without uploading your transcript.
You don’t need a sidecar. Approve commands and file changes in your agent’s own app.

Download /integrations/fisshing-claude-plugin.tar.gz and read
/integrations/claude-plugin/README.md. Start Claude with --plugin-dir and set
FISSHING_API_KEY from the character you play. Each session gets a separate
agent token. It can update its own status and fetch its replies only.
Codex setup: /integrations/fisshing-codex/README.md.
OpenCode setup: /integrations/opencode-plugin/README.md.

Player-key routes: GET/POST /api/v2/agents; DELETE /api/v2/agents/:id.
Agent-token routes: PATCH /api/v2/agents/:id;
POST /api/v2/agents/:id/messages; GET /api/v2/agents/:id/replies?after=0.
Questions have stable IDs and expiry times. Player replies must name the
exact question. Expired or unrelated replies cannot answer a new question.
Claude Code and OpenCode keep their local question UI if the plugin cannot get an
answer. Codex reports a pending or expired question, so the agent can ask locally.

## Bots

POST /api/v2/bot/enable once per game session. This displays a BOT tag.
POST /api/v2/actions with {"action":"move","direction":"up"}.
Actions: cast, hook, reel, sell_all, move, travel, interact, enter, escape, key.
Move needs direction: up, down, left, right. Travel needs a valid location.
Key needs a nonempty key string and sends its first character.

An ok response means the game input was queued. It does not confirm a catch,
move, or sale. Read state or wait for a game event to confirm a result. Do not
retry a mutating request automatically. Actions allow up to 10 requests/second
in total, with lower limits for individual actions. A 429 response means slow
down. A 403 means bot mode is not enabled. A 404 means the player is offline.

## Signed webhooks

GET /api/v2/webhooks — list your endpoints.
POST /api/v2/webhooks with {"url":"https://your-app.example/events","events":["catch","new_species"]}.
DELETE /api/v2/webhooks/:id — remove an endpoint and cancel queued deliveries.
POST /api/v2/webhooks/:id/test — send a test event.
GET /api/v2/deliveries — inspect recent delivery attempts.
POST /api/v2/deliveries/:id/replay — request a new attempt for a failed delivery.

Save the secret returned at registration. The request header
X-Fisshing-Signature contains sha256= followed by HMAC-SHA256(secret, raw body)
as lowercase hex. Verify the exact request bytes with a constant-time compare
before parsing JSON. The starter kit includes a verifier and HTTP receiver.

Events contain id, schema_version, event, player_id, timestamp, and data. Use the
stable event id to prevent duplicate effects. Return a 2xx response promptly.
The delivery service can retry network failures, 429, and 5xx responses. Each
player can register up to three public HTTPS endpoints. Private addresses and
redirects are blocked. Delivery history is limited. Save events in your own database if you need to keep them.

Events: catch, new_species, achievement, level_up, sell, world_record,
session_start, session_end, challenge_result. Use ["*"] for all game events.

## Stream tools

The Phoenix socket at /socket/overlay uses topic overlay:lobby and emits
game_event messages. Catch events include ASCII art and a rarity color.
This is a live feed with no replay. Keep its API key and URL in a private OBS
setup. Use webhooks for a service that must recover after a disconnect.
See /docs/recipes#stream-overlay for the full socket protocol and display code.

## Compatibility

Existing /api routes remain available. Use /api/v2 for new clients. Agent,
shell, editor, and overlay recipes remain at /docs/recipes. The reference below
describes the older routes for existing clients.

---

# Fisshing API Documentation

Base URL: `https://fisshing.net`
Auth: `Authorization: Bearer YOUR_API_KEY` (find your key in-game at Profile → API Key)

---

## Notifications

### POST /api/notify

Send an in-game notification (max 80 chars). Shows as a chat bubble and status bar message.

**Parameters:**
- `message` (required) — string, max 80 characters
- `source` (optional) — string, max 20 chars. Prefixed to the message display
- `alert_level` (optional) — `success` | `info` | `warning` | `error` | `record`
- `dismiss_interval` (optional) — integer, 1–30000 ms (default 5000)

```sh
curl -X POST https://fisshing.net/api/notify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Build complete!", "source": "my-agent"}'
```

### POST /api/status

Toggle an animated spinner next to your name on the map, visible to all players.

**Parameters:**
- `active` (required) — boolean. `true` to show spinner, `false` to hide

```sh
curl -X POST https://fisshing.net/api/status \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active": true}'
```

---

## Player API

### GET /api/state

Returns your gold, location, skills, and totals. Works online and offline.

```sh
curl https://fisshing.net/api/state \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**
```json
{"online": true, "gold": 1250, "location": "marina", "rod": "carbon_rod",
 "total_caught": 47, "total_gold": 3200,
 "skills": {"fishing": {"level": 12, "xp": 850}, "cooking": {"level": 3, "xp": 40}}}
```

### GET /api/fish

Returns your fish inventory with species, weight, rarity, and value.

```sh
curl https://fisshing.net/api/fish \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**
```json
{"fish": [{"species": "bass", "name": "Bass", "weight": 4.2, "rarity": "common",
  "value": 15, "cooked": false, "burnt": false}], "count": 5, "online": true}
```

---

## Webhooks

Register URLs to receive HTTP POST notifications when game events happen. Max 3 per player.

### POST /api/webhooks

Create a webhook. Provide a URL and a list of event types.

```sh
curl -X POST https://fisshing.net/api/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hook", "events": ["catch", "level_up"]}'
```

### GET /api/webhooks

List your registered webhooks (secrets are masked).

```sh
curl https://fisshing.net/api/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### DELETE /api/webhooks/:id

Remove a webhook by its ID.

```sh
curl -X DELETE https://fisshing.net/api/webhooks/wh_WEBHOOK_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Event Types

- `catch` — species, weight, rarity
- `level_up` — skill, old/new level
- `achievement` — id, name, category
- `sell` — fish, value, gold_after
- `world_record` — species, name, weight
- `session_start` / `session_end`
- `new_species` — first catch of species
- `challenge_result` — dev challenge outcome

Use `["*"]` to subscribe to all events.

### Payload & Signature Verification

```
POST your-url
Content-Type: application/json
X-Fisshing-Signature: sha256=<hex>

{"event": "catch", "player_id": "alice", "timestamp": "2025-...", "data": {...}}
```

Verify with HMAC-SHA256: compute `HMAC(secret, body)` and compare to the signature header.

---

## Bot API

Play the game programmatically. Requires an active SSH session and explicit bot mode opt-in.
Bot players show a `[BOT]` tag to other players.

### POST /api/bot/enable

Enable bot mode for your session. Must be called before any actions. Cannot be undone for the session.

```sh
curl -X POST https://fisshing.net/api/bot/enable \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

### POST /api/action

Perform an in-game action. Your SSH session must be running and bot mode enabled.

```sh
curl -X POST https://fisshing.net/api/action \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "cast"}'
```

### Actions

- `cast` — cast your rod
- `hook` — hook a bite
- `reel` — reel in
- `sell_all` — sell fish (S key)
- `move` + `direction` — up/down/left/right
- `travel` + `location` — marina, pond, ocean, etc.
- `interact` — press E
- `enter` / `escape`
- `key` + `key` — any single character

### Rate Limits

Global: 10 actions/sec. cast/hook/reel: 1/sec. sell/buy: 2/sec. Movement: 10/sec.

### Security

Actions inject raw keypresses into your SSH session. All game-state validation
(fishing state machine, gold balance, etc.) applies identically. Bots cannot skip
mini-game states or access other sessions.

---

## Collection API

### GET /api/collection

Lifetime catch stats, personal bests, and completion tracking.
Returns species counts, best weights, completion percentage, and earned achievements.

```sh
curl https://fisshing.net/api/collection \
  -H "Authorization: Bearer YOUR_API_KEY"
```

---

## Stream Overlay WebSocket

Real-time game events over WebSocket for OBS browser source overlays.
Connect from any language or a plain `<script>` tag — no framework required.

### Connect

WebSocket URL: `wss://fisshing.net/socket/overlay?token=YOUR_API_KEY&vsn=2.0.0`

Once connected, join the `overlay:lobby` channel by sending:

```json
[ref, ref, "overlay:lobby", "phx_join", {}]
```

Game events arrive as `game_event` messages on that channel. All webhook event types
are supported.

### Catch event extras

Catch events include `color` (hex, mapped from rarity) and `ascii_art` from the
fish catalog — perfect for rendering catch cards on stream.

**Rarity Colors:**
- Common: `#9ca3af`
- Uncommon: `#22c55e`
- Rare: `#3b82f6`
- Epic: `#a855f7`
- Legendary: `#f97316`

### Vanilla JS example

No dependencies — uses the raw WebSocket protocol. Save as an HTML file, replace
`YOUR_API_KEY`, and add as a Browser Source in OBS:

```js
const API_KEY = "YOUR_API_KEY";
const url = "wss://fisshing.net/socket/overlay?token=" + API_KEY + "&vsn=2.0.0";

let ref = 0;
const nextRef = () => String(++ref);

function connect() {
  const ws = new WebSocket(url);
  ws.onopen = () => {
    ws.send(JSON.stringify([nextRef(), nextRef(), "overlay:lobby", "phx_join", {}]));
  };
  ws.onmessage = (msg) => {
    const [joinRef, msgRef, topic, event, payload] = JSON.parse(msg.data);
    if (event === "game_event") renderEvent(payload);
  };
  ws.onclose = () => setTimeout(connect, 3000);
}

function renderEvent({ event, data }) {
  if (event === "catch") {
    const el = document.createElement("div");
    el.className = "catch-card";
    el.style.borderColor = data.color;
    el.innerHTML = "<pre>" + (data.ascii_art || "") + "</pre>"
      + "<strong>" + data.species + "</strong> "
      + data.weight + " lb — " + data.rarity;
    document.body.appendChild(el);
    setTimeout(() => el.remove(), 5000);
  }
}

connect();
```

### Phoenix JS example

If you prefer the `phoenix` npm package, it handles heartbeats and reconnection for you:

```js
// npm install phoenix
import { Socket } from "phoenix";

const socket = new Socket("wss://fisshing.net/socket/overlay", {
  params: { token: "YOUR_API_KEY" }
});
socket.connect();

const channel = socket.channel("overlay:lobby", {});

channel.on("game_event", ({ event, data }) => {
  if (event === "catch") {
    console.log(
      `Caught ${data.species} (${data.rarity}) — ${data.weight} lb`,
      data.ascii_art
    );
    // render your overlay here
  }
});

channel.join()
  .receive("ok", () => console.log("Connected to overlay channel"))
  .receive("error", (err) => console.error("Join failed", err));
```

---

## Developer Challenges

Push custom quiz challenges into your game session via API. An NPC spawns on the map —
walk over, press E, answer the quiz. Results fire a `challenge_result` webhook.

### POST /api/challenge

Create a challenge. Max 1 pending per player.

```sh
curl -X POST https://fisshing.net/api/challenge \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "What does handleAuth return on 401?",
    "options": ["null", "{error: unauthorized}", "throws Error", "undefined"],
    "correct": 1,
    "npc_name": "Code Review Bot",
    "time_limit_seconds": 20
  }'
```

### GET /api/challenge

Check the current challenge status.

```sh
curl https://fisshing.net/api/challenge \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### DELETE /api/challenge

Cancel a pending challenge.

```sh
curl -X DELETE https://fisshing.net/api/challenge \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Webhook Event: challenge_result

Fires when a challenge is answered (success/fail/timeout). Includes challenge_id,
prompt, result, correct_answer, player_answer, and time_taken_ms.
