# Kofra

> Instant web hosting for AI agents (Kofra, open source, on Cloudflare). Publish a file, folder or site to a live URL with one call; every publish is an immutable version you can roll back. Use the REST API, the remote MCP server or the Agent Skill.

Base URL: https://kofra.app
Auth: `Authorization: Bearer dw_...` (API key from the instance owner; scopes sites:read, sites:write, sites:delete, keys:write, members:write, data:write, secrets:write, admin). Anonymous publishing is disabled on this instance.
Errors: JSON `{ "error": { "code", "message", "hint", "retryable", "retryAfterSeconds", "docs", "next", "details" }, "requestId" }`. Follow the hint and next calls; validation errors list bad fields in details.fields. Catalog: https://kofra.app/docs/errors.md.
URLs: share the `url` from responses exactly as returned (site URLs look like https://bright-otter-x7k2.kofra.io/).

## Quick start

- Publish: `curl https://kofra.app/api/v1/publish -H "Authorization: Bearer $KEY" -F "index.html=@index.html" -F "assets/app.js=@assets/app.js"` (form field name = file path). Also accepts a .zip body, JSON `{"files":[{"path","content"}]}`, or one raw file with `?path=name.ext`.
- Same site again: add `slug=<slug>`. Identical content returns `unchanged`.
- Change some files: `PATCH https://kofra.app/api/v1/sites/{slug}/files` with `files`, `delete`, `move`, `baseVersionId`.
- Large sites: `POST /api/v1/sites/{slug}/versions` with `{path,size,sha256}` for every file, `PUT /api/v1/blobs/{sha256}` only for the missing ones, then `POST .../versions/{id}/finalize`. The old `/deploys` paths still work as aliases.
- Viewing: browsers get rendered views (HTML is the real page, Markdown rendered); `?raw=1` returns raw bytes, `?download=1` downloads.

## MCP

- Endpoint: `https://kofra.app/mcp` (Streamable HTTP; header `Authorization: Bearer dw_...`). Narrow the tool list with `?toolsets=core` (or sites, versions, keys, members, folders, transfers) and `?read_only=1`.
- Claude Code: `claude mcp add --transport http kofra https://kofra.app/mcp --header "Authorization: Bearer $KOFRA_API_KEY"`
- Codex (~/.codex/config.toml): `[mcp_servers.kofra]` with `url = "https://kofra.app/mcp"` and `bearer_token_env_var = "KOFRA_API_KEY"`
- Cursor, VS Code and other clients: add a remote (HTTP) MCP server with that URL and header.
- Inspector: `npx @modelcontextprotocol/inspector --cli https://kofra.app/mcp --transport http --header "Authorization: Bearer $KOFRA_API_KEY" --method tools/list`

## Docs

- [Full reference](https://kofra.app/llms-full.txt): every endpoint, every MCP tool with its input schema, error codes
- [OpenAPI 3.1](https://kofra.app/openapi.json): x-mcp-tool names available MCP twins; x-mcp-exclude explains REST-only operations
- [Agent Skill](https://kofra.app/skill.md): install with `npx skills add https://kofra.app` or `npx skills add TogetherWeOwn/kofra`
- [Source (MIT)](https://github.com/TogetherWeOwn/kofra)

## REST endpoints

- `GET /api/v1/me` (whoami): Who am I
- `POST /api/v1/publish-sources` (publish_site): Publish a site
- `PATCH /api/v1/sites/{slug}/files` (update_site_files): Change some files
- `GET /api/v1/sites/{slug}/files` (list_files): List files
- `POST /api/v1/sites/{slug}/versions/{id}/finalize` (finalize_version): Make an uploaded version live
- `GET /api/v1/sites/{slug}` (get_site): Get a site
- `PATCH /api/v1/sites/{slug}` (update_site_settings): Change site settings
- `DELETE /api/v1/sites/{slug}` (delete_site): Delete a site
- `GET /api/v1/sites` (list_sites): List sites
- `POST /api/v1/sites` (create_site): Create an empty site
- `GET /api/v1/read-file` (read_file): Read a file
- `GET /api/v1/sites/{slug}/versions` (list_versions): List versions
- `POST /api/v1/sites/{slug}/versions` (create_version): Start a version from a file manifest (upload only what is missing)
- `POST /api/v1/sites/{slug}/versions/{id}/restore` (restore_version): Restore version
- `PUT /api/v1/sites/{slug}/access` (set_site_access): Set who can open a site
- `POST /api/v1/sites/{slug}/claim` (claim_site): Claim an anonymous site
- `POST /api/v1/sites/{slug}/rename` (rename_site): Rename a site
- `POST /api/v1/sites/{site}/duplicate` (duplicate_site): Duplicate a site's live version into a new slug
- `POST /api/v1/slugs/release` (release_slug): Release a held site name early
- `GET /api/v1/sites/{slug}/versions/{id}` (get_version): Get a version
- `DELETE /api/v1/sites/{slug}/versions/{id}` (delete_version): Delete version
- `GET /api/v1/sites/{slug}/versions/{version}/export` (export_version): Download a version as a ZIP
- `POST /api/v1/uploads` (create_multipart_upload): Start or resume a multipart upload
- `GET /api/v1/uploads/{id}` (get_multipart_upload): Show a resumable upload's acknowledged parts
- `PUT /api/v1/uploads/{id}/parts/{partNumber}` (upload_multipart_part): Upload one part's raw bytes
- `POST /api/v1/uploads/{id}/complete` (complete_multipart_upload): Complete and verify a resumable upload
- `GET /api/v1/keys` (list_api_keys): List API keys
- `POST /api/v1/keys` (create_api_key): Create an API key
- `DELETE /api/v1/keys/{id}` (revoke_api_key): Revoke an API key
- `GET /api/v1/members` (list_members): List members
- `POST /api/v1/members` (add_member): Add a member
- `PATCH /api/v1/members/{id}` (update_member): Change a member
- `DELETE /api/v1/members/{id}` (remove_member): Remove a member
- `DELETE /api/v1/folders/{folder}` (delete_folder): Delete a folder
- `GET /api/v1` (api_index): API index with links to the docs
- `POST /api/v1/setup` (setup_instance): First run: create the owner (bootstrap key only)
- `POST /api/v1/setup/recover` (recover_owner_key): Mint a new owner key (bootstrap key only)
- `POST /api/v1/publish` (publish_upload): Publish files in one call and get a live URL
- `GET /api/v1/sites/{slug}/deploys/{id}` (get_deploy): Pending version status (missing files)
- `PUT /api/v1/blobs/{sha256}` (upload_blob): Upload one file's raw bytes
- `GET /api/v1/sites/{slug}/files/{path}` (get_file_bytes): Read one file's raw bytes (ETag = sha256)
- `POST /api/v1/transfers` (create_transfer): Create a file delivery
- `GET /api/v1/transfers/{transfer}` (get_transfer): Read your file delivery
- `PATCH /api/v1/transfers/{transfer}` (update_transfer): Edit an unnotified live delivery
- `POST /api/v1/transfers/revoke` (revoke_transfers): Revoke file deliveries
- `GET /api/v1/operations` (list_operations): List API operations and CLI commands

## MCP tools

- `whoami`: Who am I
- `publish_site`: Publish a site
- `update_site_files`: Change some files
- `finalize_version`: Make an uploaded version live
- `get_site`: Get a site
- `list_sites`: List sites
- `list_files`: List files
- `read_file`: Read a file
- `list_versions`: List versions
- `restore_version`: Restore version
- `update_site_settings`: Change site settings
- `set_site_access`: Set who can open a site
- `delete_site`: Delete a site
- `create_site`: Create an empty site
- `rename_site`: Rename a site
- `duplicate_site`: Duplicate a site's live version into a new slug
- `release_slug`: Release a held site name early
- `get_version`: Get a version
- `delete_version`: Delete version
- `list_api_keys`: List API keys
- `create_api_key`: Create an API key
- `revoke_api_key`: Revoke an API key
- `list_members`: List members
- `add_member`: Add a member
- `update_member`: Change a member
- `remove_member`: Remove a member
- `delete_folder`: Delete a folder
- `create_transfer`: Create a file delivery
- `get_transfer`: Read your file delivery
- `update_transfer`: Edit an unnotified live delivery
- `revoke_transfers`: Revoke file deliveries
- `get_account`: The account this credential acts for (handle, kind personal or workspace, your role) and the person behind it, with links to the dashboard, usage and plan.
- `list_workspaces`: Every account of the person behind this credential: their personal account and each workspace, with handle and role.
- `create_workspace`: Create a team workspace owned by the person behind this credential (plan limits apply; over the limit returns limit_reached with an upgradeUrl for a human).
- `create_invite`: Create a one-use invite link (valid 7 days) into the workspace this credential acts for.
- `accept_invite`: Join a workspace with an invite token (dwi_..., the token= part of an invite link), as the person behind this credential.
- `export_account`: Build a zip of every site's live files plus a JSON manifest of versions and settings for the account this credential acts for.
- `list_plans`: Plans, prices (USD cents), limits and add-ons of this hosted service.
- `get_plan`: The plan, billing status and entitlements of the account this credential acts for.
- `start_checkout`: Returns a URL for a HUMAN to open.
- `open_billing_portal`: Returns a URL for a HUMAN to manage payment methods, invoices and cancellation.
- `list_invoices`: Invoices of the account this credential acts for (newest first; amounts in the currency's smallest unit), each with a hosted URL a HUMAN can open to view or pay it.
- `get_usage`: Plan limits, current use (storage, sites, members, publishes in the last hour) and 30 days of counters for this account.

## Hosted service: accounts, plans and usage
- Sign up or sign in without a browser: `POST https://kofra.app/api/v1/auth/email/start` with `{"email": "..."}`; the person receives a link containing a `dwm_...` token; `POST https://kofra.app/api/v1/auth/email/verify` with `{"token": "dwm_..."}` returns an API key.

- Plans and prices: https://kofra.app/pricing.md (or /pricing.json). Your plan: `GET /api/v1/billing/plan`. Your use and limits: `GET /api/v1/usage`.
- Upgrades: `POST /api/v1/billing/checkout` with `{"plan": "pro", "interval": "month", "currency": "usd", "acknowledgedPriceCents": 800}` (the price you showed the user, from /pricing.json) returns a URL for a HUMAN to pay. With a Stripe shared payment token (`sharedPaymentToken`) the URL is an approval page where a signed-in owner or admin must approve before anything is charged. Never ask for card details.
- Hitting a limit returns HTTP 402 `limit_reached` with `details: {key, limit, used, plan, upgradeUrl}`.
- Teams: `GET|POST /api/v1/workspaces`, `POST /api/v1/workspaces/{handle}/invites`, `POST /api/v1/invites/accept` (MCP `list_workspaces`, `create_workspace`, `create_invite`, `accept_invite`).

## Hosted REST endpoints (operationId = MCP tool name)

- `POST /api/v1/auth/email/start` (start_email_sign_in; REST only): Sign up or sign in: email a one-time link
- `POST /api/v1/auth/email/verify` (verify_email_sign_in; REST only): Exchange the token from the link for an API key
- `POST /api/v1/setup` (admin_create_account; REST only): Operators: create a hosted account with the bootstrap key
- `GET /api/v1/account` (get_account; MCP `get_account`): The account this credential acts for
- `GET /api/v1/workspaces` (list_workspaces; MCP `list_workspaces`): Accounts (personal and workspaces) of the person behind this credential
- `POST /api/v1/workspaces` (create_workspace; MCP `create_workspace`): Create a workspace
- `POST /api/v1/workspaces/{handle}/invites` (create_invite; MCP `create_invite`): Create a one-use invite link (7 days)
- `POST /api/v1/invites/accept` (accept_invite; MCP `accept_invite`): Accept an invite
- `POST /api/v1/account/exports` (export_account; MCP `export_account`): Build a zip of every live file plus a manifest (7 days to download)
- `GET /api/v1/account/exports` (list_account_exports; REST only): List this account's exports
- `GET /api/v1/account/exports/{id}` (get_account_export; MCP `get_account_export`): Poll one export
- `GET /api/v1/account/exports/{id}/download` (download_account_export; REST only): Download the finished zip
- `GET /api/v1/account/deletion` (get_account_deletion; REST only): Is this account deleted, and when does it purge?
- `POST /api/v1/account/deletion/confirm` (confirm_account_deletion; REST only): Delete this account (owners type the handle)
- `GET /api/v1/usage` (get_usage; MCP `get_usage`): Plan limits, current use and 30 days of counters
- `GET /api/v1/billing/plans` (list_plans; MCP `list_plans`): Plans, prices (USD cents) and limits
- `GET /api/v1/billing/plan` (get_plan; MCP `get_plan`): This account's plan, status and entitlements
- `POST /api/v1/billing/checkout` (start_checkout; MCP `start_checkout`): Start an upgrade: returns a URL for a human to pay or approve
- `POST /api/v1/billing/portal` (open_billing_portal; MCP `open_billing_portal`): Billing portal URL for a human
- `GET /api/v1/billing/invoices` (list_invoices; MCP `list_invoices`): Invoices of this account
