> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# plungeai-in-v0

> Connect v0 (Vercel) to PlungeAI (Ocean Studio) over MCP — the + menu → MCPs form, why the generated Next.js app must call PlungeAI through a server Route Handler (v0 apps can't call MCP directly), and a plungeai_whoami + plungeai_list_agents verify.


Connect v0 (Vercel) to PlungeAI (Ocean Studio) over MCP — the + menu → MCPs form, why the generated Next.js app must call PlungeAI through a server Route Handler (v0 apps can't call MCP directly), and a plungeai_whoami + plungeai_list_agents verify. Use when the user is in v0 and says connect / install / set up PlungeAI, mentions the MCPs panel, or a generated v0 app 401s against PlungeAI. For another builder use its plungeai-in-<tool> skill; for an unlisted but MCP-capable client use plungeai-mcp-setup's generic client config.

[Download zip](https://skills.plungeai.com/plungeai-in-v0.zip) · [View raw SKILL.md](https://skills.plungeai.com/plungeai-in-v0/SKILL.md)

v0 is a hosted builder: no local config file and no JSON entry anywhere —
the MCP form is the only surface, and the key lives only in that entry.

## Prerequisites

- An `ozk_` key: Dashboard → **One API → Keys** (https://dashboard.plungeai.com) —
  self-service, shown once, copy it now.

## Connect (once)

Open the **+** menu in the prompt form → **MCPs** → add your own server (the
project settings page offers the same via **Add MCP**):

| Field | Value |
|---|---|
| Name | `PlungeAI` |
| URL | `https://mcp.plungeai.com/v1` |
| Authentication | **Bearer Token** → paste the `ozk_` key itself (v0 adds the `Bearer` prefix); or **Custom Headers** → `Authorization` = `Bearer ozk_YOUR_KEY`. If one form 401s, try the other. (`No Auth` and `OAuth` don't apply — PlungeAI OAuth hasn't shipped.) |

Then pick the server's **permission mode**: **Disabled**, **Ask for
Approval (Manual)**, or **Always Run (Auto)**. Manual is the sane default;
money-verb PlungeAI operations still pause with a structured
`needs_approval` outcome even under Auto — relay it, then
`plungeai_continue`.

## The generated app cannot call MCP

v0's generated code cannot use the MCP tools directly — pair the MCP with
**Environment Variables**. So the split is: the builder chat operates
PlungeAI over MCP; the generated Next.js app calls the One API server-side
only, e.g. a Route Handler:

    // app/api/plungeai/route.ts
    export async function POST(req: Request) {
      const { input, agentId } = await req.json()
      const r = await fetch(
        `https://api.plungeai.com/v1/agents/${agentId}/execute`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.PLUNGEAI_API_KEY}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ input, sync: true }),
        },
      )
      return Response.json(await r.json())
    }

Key in a Vercel env var (`PLUNGEAI_API_KEY`, every environment that needs
it), never in client components, never prefixed `NEXT_PUBLIC_`.

## What to tell v0 about the One API (2.3.0)

When the app needs more than one agent call, paste this block into the prompt
that asks v0 for its Route Handlers. Every route stays server-side and sets
`Authorization: Bearer ${process.env.PLUNGEAI_API_KEY}` itself.
The minimal saved-workflow body (`workflow:` / `tasks:` YAML) is in **plungeai-api-setup**.

```text
PlungeAI One API 2.3.0. Base URL https://api.plungeai.com. Spec: GET /v1/openapi.json (no auth) - never invent a route.
AUTH: Authorization: Bearer ozk_YOUR_KEY on every call; key in an env var, server side only. When a connector attaches auth, NEVER add your own Authorization header.
ME: GET /v1/whoami -> {user_id, via, tier, key_label}. GET /v1/key -> {data:{label, limit, usage, rate_limit}}.
FIND (never from memory): GET /v1/discovery/search?q=<capability>&kind=agents -> {cards} (without kind, skill cards come first). POST /v1/discovery/resolve {task}. GET /v1/agents. GET /v1/tools. GET /v1/tools/{id} = the contract.
DOOR 1, one agent or tool: POST /v1/agents/{id}/execute {prompt|input, sync?, model?} -> 200 {content, workflow_id, task_id, request_id}.
  POST /v1/tools/{id}/execute {operation, params} -> {ok, content, outcome}; 422 invalid_params echoes the contract.
DOOR 2, saved workflow or bot: POST /v1/workflows/{id}/execute {input?, inputs?} -> {success, workflow_id, final_task_id, request_id, content}, synchronous.
  POST /v1/workflows/{id}/execute-stream = SSE. Inline YAML: POST /v1/workflows/execute. No content? GET /v1/workflows/results/{workflowId}/{taskId} (taskId = final_task_id).
DOOR 3, Agent Call: POST /v1/execute, the body is the program: {agent:"<saved id>[@n]", input} or {workflow:{...}} or {tasks:[...]}. dry_run:true plans without running;
  on_behalf_of:"<customer id>" uses that customer's connections.
ASYNC: POST /v1/agents/{id}/execute {sync:false} -> 202 {workflow_id, task_id, request_id}; redeem GET /v1/agents/results/{workflowId}/{taskId} -> {content}; 404 not_ready = poll, 2s+.
EXECUTIONS: the run id is the body's workflow_id (also the x-execution-id header, and your x-trace-id UUID if you sent one; replaying one is 409 duplicate_execution_id).
  GET /v1/executions (source, status, workflow_id). GET /v1/executions/{id}: running | completed | incomplete | failed | cancelled | needs_input | needs_approval.
  GET /v1/executions/{id}/output -> {content, steps}. GET /v1/executions/{id}/files. GET /v1/executions/{id}/conversation. DELETE /v1/executions/{id}. GET /v1/executions/{id}/stream = SSE.
  POST /v1/executions/{id}/continue {answer | approve, allow_scope: once|run|always}. POST /v1/executions/{id}/followup {text}. POST /v1/executions/{id}/cancel.
  POST /v1/executions/{id}/steer {text} queues a message for a live run (202), else 409 execution_finished.
WORKFLOWS: GET|POST /v1/workflows. GET|PATCH|DELETE /v1/workflows/{id}. GET|POST /v1/workflows/{id}/versions. POST /v1/workflows/{id}/versions/{versionId}/restore.
  Save needs {name, kind, yaml}; kind = workflow|agent|bot|campaign. GET /v1/workflows lists every kind unless ?kind=bot (or campaign). 422 invalid_workflow lists errors, writes nothing.
SCHEDULES: POST /v1/schedules {name, schedule, workflow_id, timezone?, parameters?}; schedule = a cron, or "@once" with run_at (ISO-8601 WITH a UTC offset, in the future);
  400 run_at_required | run_at_past. GET /v1/schedules. GET|PATCH|DELETE /v1/schedules/{id}. GET /v1/schedules/{id}/runs (execution_id per run).
  POST /v1/schedules/{id}/run. POST /v1/schedules/{id}/pause. POST /v1/schedules/{id}/resume. POST /v1/schedules/{id}/wake {note?, in?} fires it once, now or later.
TRIGGERS (every call names schedule_id; a secret is shown once): GET|POST /v1/triggers/webhook. DELETE /v1/triggers/webhook/{token}. POST /v1/triggers/webhook/{token}/rotate.
  The same four under /v1/triggers/email {allowed_from}. GET|POST /v1/triggers/hooks {url, events}. DELETE /v1/triggers/hooks/{id}.
PERMISSIONS: GET|PUT /v1/workflows/{id}/permissions {classes:{send|write|pay|delete: allow|ask|deny|hand_off}, locks, budget_usd_run, budget_usd_month}; locks need a team admin (403 lock_admin_required).
BOT DATA: GET /v1/workflows/{id}/workspace-files. GET /v1/workflows/{id}/workspace-files/download?path=. GET /v1/workflows/{id}/inbox. GET|POST /v1/workflows/{id}/bindings {channel, sender_id}.
  DELETE /v1/workflows/{id}/bindings/{channel}/{sender_id}. GET /v1/channels. POST /v1/channels/pair {channel}.
  GET|POST /v1/bot-groups {name, members = 2-6 of your bot ids}. PUT|DELETE /v1/bot-groups/{id}. GET /v1/bot-groups/{id}/thread.
MEMORY: GET /v1/memory/recall?q=. POST /v1/memory/remember {text, scope: memory|user}. POST /v1/memory/forget {id | text}. GET /v1/memory/runs?q= (q required). GET /v1/memory/runs/{id}.
SKILLS: GET /v1/skills. POST /v1/skills/learn {name, body} saves the body you send (it does not distill a URL). DELETE /v1/skills/{id}.
TEMPLATES: GET /v1/templates?kind=workflow|agent|bot. GET /v1/templates/{id}. POST /v1/templates/{id}/use {name?} -> 201 {workflow}.
CONNECTIONS: GET /v1/connections. POST /v1/connections/{provider} {api_key, on_behalf_of?}. DELETE /v1/connections/{provider}. POST /v1/connections/{provider}/link -> {url, expires_at}.
MODELS (OpenAI-shaped, Bearer only): GET /v1/models. POST /v1/chat/completions. POST /v1/embeddings.
TRACES: GET /v1/traces/{id}, id = the run's workflow_id.
LISTS: {workflows|executions|schedules|cards:[...], total, limit, offset, has_more}; POST /v1/schedules -> {schedule}.
ERRORS: {"error":{"code","message"}} plus an X-Error-Code header. 400 validation; 401 unauthorized; 403 refused | agent_not_active; 404 <thing>_not_found (a stranger's id too);
  409 approval_required, or not_paused | execution_finished | duplicate_execution_id | name_taken; 422 invalid_params; 429 rate_limited + Retry-After; 502/503 retry once.
  424 connection_required | credential_required carries error.connect {provider, method, link}: connect via POST /v1/connections/{provider} or its /link.
FENCES: 403 refused is final - surface it, never retry. 409 approval_required is a human boundary - never approve for the user. tools/{id}/execute: a human approves in Studio, then re-issue.
  A paused agent reply: re-send with approved:true plus its action_token (both). A run reading needs_approval: POST /v1/executions/{id}/continue, never the original call.
```

## Verify

1. Builder chat: "use plungeai_whoami to confirm my identity" → an
   identity card.
2. Generated app (server side):
   `curl -H "Authorization: Bearer ozk_YOUR_KEY" https://api.plungeai.com/v1/agents`
   → HTTP 200 JSON.
3. "use plungeai_list_agents to search 'web search'" → live results from
   the active agent catalog.

## Quirks

- v0 previews run without the project's env vars until linked/deployed — a
  preview 401 from the app does not mean the integration is wrong; verify
  with the curl check above, then set the env var on the Vercel project.
- If generation puts the fetch in a `"use client"` component, regenerate
  with: "PlungeAI calls go through a Route Handler; the key stays
  server-side."
- Long CNL workflows: prefer async mode
  (`plungeai_execute_workflow` with `mode: "async"` +
  `plungeai_get_workflow_status`) over one long synchronous builder-chat
  call.

## Where next

- Writing the Route Handler / server code that calls `https://api.plungeai.com`: **plungeai-api-setup**.
- Operating the `plungeai_*` tools in chat: **plungeai-mcp-setup**.
- Picking MCP vs the One API for a given job: **choose-your-plungeai-door**.
- An unlisted but MCP-capable client, or the shared connect concepts (native
  remote vs. stdio bridge, key hygiene): **plungeai-mcp-setup**.
- Full guide: https://mcp.plungeai.com/docs#3-connecting-clients
