form·vee API

Public developer manual for the form·vee REST API and MCP server. Use workspace API keys from /settings/api-keys to create and publish forms, and to read completed responses from scripts or AI agents.

OpenAPI 3.1.0 API 1.0.0 MCP 1.0.0 https://form.vee.kr/api/v1

Getting started

  1. Sign in and open /settings/api-keys.
  2. Create a key with only the scopes your client needs.
  3. Copy the plaintext key once (fv_<prefix>_<secret>). It is never shown again.
  4. Call REST at https://form.vee.kr/api/v1 or connect an MCP client to https://form.vee.kr/api/mcp.
Keys are bound to one workspace. If the issuing user loses owner/admin membership, existing keys stop working immediately.

Authentication

Workspace API key. Available scopes: forms:read, forms:write, forms:publish, responses:read, responses:partial:read, assets:write, files:read.

Send the key on every request:

Authorization: Bearer fv_<prefix>_<secret>

OpenAPI JSON at /api/v1/openapi.json is the only unauthenticated public machine-readable surface (CORS *). All other /api/v1 and /api/mcp routes require a valid Bearer key.

Scopes

Scopes are granted at key creation time. Missing scope returns 403 forbidden. Response creation is not available over API or MCP; respondents submit through the public form.

Errors & rate limits

Authenticated JSON errors use:

{
  "ok": false,
  "error": {
    "code": "conflict",
    "message": "Human-readable summary",
    "details": {}
  }
}

Common codes: unauthorized, forbidden, not_found, invalid, conflict, unprocessable, rate_limited, payload_too_large, unsupported_kind, internal.

Successful authenticated responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and X-Request-Id. Defaults are 120 requests/minute per key and 600 auth attempts/minute per IP. 429 responses also include Retry-After. Inspect remaining budget with GET /api/v1/usage.

REST quick start

List forms:

curl -sS \
  -H "Authorization: Bearer $FORMVEE_API_KEY" \
  "https://form.vee.kr/api/v1/forms"

Create a classic draft, then publish with the returned revision:

curl -sS -X POST \
  -H "Authorization: Bearer $FORMVEE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"classic","title":"NPS survey"}' \
  "https://form.vee.kr/api/v1/forms"

# After editing the draft definition via PATCH, publish:
curl -sS -X POST \
  -H "Authorization: Bearer $FORMVEE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expectedDraftRevision":1}' \
  "https://form.vee.kr/api/v1/forms/<form-id>/publish"

Success bodies are wrapped as { "data": ... } except DELETE (204), CSV streams, file redirects (302), and the raw OpenAPI document.

REST endpoints

Paths below are relative to https://form.vee.kr/api/v1. Required scopes come from the OpenAPI x-scope extension used by the runtime.

Method Path Scope Description
GET /capabilities auth only Returns the calling key's scopes and the workspace-evaluated form kinds, render modes, block types, and allowlisted authoring features. A valid key is sufficient; each mutation still enforces its own scope and feature gates.
GET /usage auth only Any valid key (no specific scope). Returns the key's scopes, the current per-minute rate-limit budget (same values as the X-RateLimit-* response headers), and workspace request counts over the last 24h/7d.
GET /forms forms:read Workspace forms returned as `{ items, nextCursor }`, ordered by updatedAt/id descending. Search matches title or slug; kind and status are optional exact filters.
POST /forms forms:write `kind` selects classic (default) or interview; `definition` is validated against the matching schema (FormDefinition for classic, InterviewDefinition for interview). `definition`/`settings` are optional; omitting them starts an empty draft of that kind. A classic definition that uses a feature not enabled for the workspace (file_upload/ranking/matrix) is rejected with 422.
GET /forms/{id} forms:read Single form with its draft definition and settings. Supports both kinds; `draftDefinition` is a FormDefinition (classic) or InterviewDefinition (interview).
PATCH /forms/{id} forms:write Optimistic concurrency. Classic forms use FormDefinition; interview forms use InterviewDefinition. Returns 409 on revision conflict.
DELETE /forms/{id} forms:write Soft delete.
GET /forms/{id}/versions forms:read Bounded immutable publication history returned as `{ items, nextCursor }` and ordered by version descending. Definitions are omitted; fetch a version detail by id to read its snapshot.
GET /forms/{id}/versions/{versionId} forms:read Returns the immutable definition used by responses pinned to this formVersionId.
POST /forms/{id}/duplicate forms:write + forms:read Requires both forms:write and forms:read. Creates a draft of the same kind after reapplying the canonical schema, current workspace feature gates, settings gates, and image-ownership checks. Legacy readable drafts are canonicalized before the new write.
POST /forms/{id}/publish forms:publish Supports both kinds. Empty forms (classic without input blocks, interview without content) return 422.
POST /forms/{id}/unpublish forms:publish Unpublish a form
PATCH /forms/{id}/slug forms:write Requires the `forms.custom-slug` feature.
PATCH /forms/{id}/settings forms:write Replaces theme, completion, submission, and document settings using optimistic concurrency. Pass the settingsSignature returned by GET /forms/{id} or the preceding successful write.
POST /forms/{id}/assets assets:write Uploads one image for this form. The server validates PNG, JPEG, GIF, or WebP by magic bytes and caps the file at 5 MiB. The returned key may be used by image blocks, choice images, logos, and document covers.
GET /forms/{id}/responses responses:read Kind-aware, filtered keyset page returned as `{ items, nextCursor }`. Compact is the default and never includes interview transcript text; use projection=full for answers/files or transcript. status=partial additionally requires `responses:partial:read` and the partial-submissions feature. Partial responses are a live mutable view, so cursor traversal is best-effort rather than snapshot-isolated.
GET /forms/{id}/reports responses:read Classic forms return per-question and forms.scoring score summaries plus optional analytics.basic aggregates. Interview forms return the current V2 funnel, analysis coverage, and qualitative-code aggregates. Classic text summaries can contain recent answer samples for responses:read clients; interview reports never contain transcript text. The q filter applies to classic question summaries, while analytics remains the selected date cohort.
GET /forms/{id}/responses/summary responses:read Per-question aggregates plus numeric score summaries when forms.scoring is enabled. Classic forms only; interview forms return 409 (use GET /forms/{id}/reports or /forms/{id}/interview-analytics for Interview V2).
GET /forms/{id}/responses/{responseId} responses:read Classic forms return a Response; interview forms return an InterviewResponse.
GET /forms/{id}/responses.csv responses:read Exports completed responses. Classic forms export stable block-id variables and support label/code values, forms.scoring adds stable numeric score_<id> variables, wide/long layout, and a conservative deidentified projection. File link columns point to the API file download route; following those links requires `files:read`. Interview forms export conversation summary, extracted fields, qualitative themes, and transcript.
GET /forms/{id}/insights responses:read Aggregates AI-extracted insights from completed classic responses into field-promotion candidates (frequency × confidence). `auto` candidates have crossed the auto-promotion threshold. classic forms only — interview forms return 409.
GET /forms/{id}/interview-analytics responses:read Breaking v1 contract: computes the current Interview V2 funnel, analysis coverage, and qualitative-code aggregates from authoritative tables. It does not read the retired legacy aggregate projection, trigger analysis jobs, or return transcripts. Classic forms return 409.
GET /forms/{id}/files/{fileId} files:read Redirects (302) to a short-lived presigned URL.

Discover workspace capabilities (kinds, render modes, block types, authoring flags) with GET /capabilities before generating definitions.

Optimistic concurrency

Pagination

List endpoints return { "data": { "items": [...], "nextCursor": "..." | null } }. Pass nextCursor unchanged as cursor for the next page. Limits are capped at 100 (forms/versions default 20, responses default 50).

MCP server

form·vee exposes a stateless Streamable HTTP MCP endpoint at https://form.vee.kr/api/mcp. It uses the same API keys, workspace boundary, scopes, and per-key rate limit as REST. The surface is task-oriented (five tools), not one tool per REST route.

Client setup

Claude Code:

claude mcp add --transport http formvee https://form.vee.kr/api/mcp \
  --header "Authorization: Bearer fv_xxx_xxx"

Any MCP client that can set a Streamable HTTP URL and Authorization header works. If the client sends Origin, it must exactly match the deployment origin.

Tools

Tools appear in tools/list only when the key holds the required scope.

Tool Scope What it does
forms_query forms:read Search forms, fetch one draft (with concurrency tokens), or read published version history/snapshots.
forms_create forms:write (+ forms:read for copy) Create a blank classic/interview draft, or copy an existing readable form through the same create gates.
forms_update forms:write Replace definition (draftRevision CAS), replace settings (settingsSignature CAS), or set a public slug.
forms_publication forms:publish Converge publish state with expectedDraftRevision and desiredState published|draft.
responses_query responses:read List completed responses (compact), fetch detail, or load a kind-aware aggregate report.

Typical agent loop:

  1. Read formvee://workspace/context for scopes, tools, and capabilities.
  2. forms_query search/get to obtain draftRevision and settingsSignature.
  3. Mutate with forms_create / forms_update, then forms_publication.
  4. Use responses_query for compact lists/reports; request transcript only when needed.
Not exposed over MCP: form delete, authoring asset upload, response creation, custom script editing, and partial response reads. Use REST for those management tasks.

Workspace context resource

URI: formvee://workspace/context

Returns the calling key's scopes, visible tools, evaluated authoring capabilities (kinds, render modes, block types, allowlisted features), and MCP-only constraints. Agents should read this before creating definitions so they avoid disabled features.

Results & privacy

Machine-readable contract

Prefer /api/v1/openapi.json for codegen and external API explorers. This HTML page is the human manual for both REST and MCP. The deployment is currently private: v1 path and OpenAPI info.version stay at 1.0.0 while contracts may still change in place.