Documentation

Everything here works two ways: with forms in the console, no code needed, or over HTTP. Each section says where to find it in the console.

Getting started

  1. Create a site. You get yoursite.vmcog.com and a secret key, shown once.
  2. Open the site from your console. Pages & files is an editor for your HTML, CSS and JavaScript; Data holds your tables; Visitor access says what the public may do with them.
  3. Prefer the terminal or an AI assistant? Everything below works with curl, fetch or the MCP server.

All endpoints live at https://api.vmcog.com, take and return JSON, and send CORS headers, so a page on any domain can call them.

Keys

KeyLooks likeUse it for
Secretsk_…Everything: SQL, uploads, MCP. Keep it on your machine or server, never in a page. Rotate it from the console if it leaks.
Publicpk_…Only /rows/:table, and only what visitor access allows. Safe to put in your page.
Authorization: Bearer sk_…

The key decides which site a request acts on; there is no site name in the URL.

SQL: POST /query secret key

In the console: SQL tab, or create tables with forms under Data.

Runs SQLite against your site's database. The body takes one of three shapes:

BodyDoesReturns
{ "sql": "…", "params": [..] }One statement. params fill ? placeholders, or pass an object for :name.{ rows } for queries, { changes, lastInsertRowid } for writes
{ "batch": [{ sql, params }, …] }Several statements in one transaction.{ results: [ … ] }
{ "script": "…; …;" }Many statements, no params, one transaction. Good for creating tables.{ ok: true }
curl -X POST https://api.vmcog.com/query -H "Authorization: Bearer sk_…" \
  -d '{"sql":"SELECT * FROM notes WHERE id > ?","params":[10]}'

Query results stop at 1000 rows and add "truncated": true. ATTACH, transactions inside a statement and writable pragmas are refused.

Rows: /rows/:table public or secret key

In the console: Data tab, a spreadsheet-style grid.

A small REST API for one table, meant for your page to call with the public key.

MethodDoesBodyNeeds
GETRead rowsselect rule
POSTInsert one row or an array of rows; returns them with their ids{ col: value } or [ … ]insert rule
PATCHUpdate the rows matching the filters; returns them{ col: value }update rule and at least one filter
DELETEDelete the rows matching the filters; returns themdelete rule and at least one filter

Query string

ParameterExampleMeaning
col=op.value?votes=gte.10Filter. op is eq, neq, gt, gte, lt, lte or like (% wildcard). Repeat to AND them.
col=is.null?deleted_at=is.nullAlso is.notnull.
select?select=id,bodyColumns to return. Defaults to all.
order?order=created.desc,idSort by .asc or .desc; defaults to ascending.
limit, offset?limit=20&offset=40Paging. limit is capped at 1000.
const api = (p, init) => fetch("https://api.vmcog.com" + p, {
  ...init, headers: { Authorization: "Bearer pk_…", "Content-Type": "application/json" } }).then(r => r.json());

await api("/rows/notes", { method: "POST", body: JSON.stringify({ body: "hello" }) });  // [{ id: 1, body: "hello" }]
await api("/rows/notes?order=id.desc&limit=20");                                       // newest 20
await api("/rows/notes?id=eq.1", { method: "PATCH", body: JSON.stringify({ body: "hi" }) });
await api("/rows/notes?id=eq.1", { method: "DELETE" });

Booleans are stored as 0 and 1; nested objects are refused. Tables whose names start with _vmcog or sqlite_ are not reachable here.

Visitor access: _vmcog_rules

In the console: Visitor access tab, one row of checkboxes per table.

Every site has a table _vmcog_rules(tbl, op, filter). The public key may perform op on tbl only if a row allows it; otherwise it gets 403. The secret key ignores rules.

ColumnMeaning
tblTable name
opselect, insert, update or delete
filterOptional SQL condition that limits which rows select, update and delete can see, e.g. published = 1. NULL means all rows.
INSERT INTO _vmcog_rules VALUES ('posts', 'select', 'published = 1'), ('comments', 'insert', NULL);

Files: /site/:path secret key

In the console: Pages & files tab, an editor with Save & publish and Upload.

MethodDoes
PUT /site/:pathCreate or replace a file. The request's Content-Type is served back to visitors. Returns { path, bytes }.
DELETE /site/:pathRemove a file. Returns { deleted }.
curl -X PUT https://api.vmcog.com/site/css/app.css -H "Authorization: Bearer sk_…" \
  -H "Content-Type: text/css" --data-binary @app.css

Files are live at https://yoursite.vmcog.com/<path> within a minute. A path ending in / serves its index.html. Paths use letters, digits, ., -, _ and /.

Custom domains

In the console: Connect your own domain on the site's card.

Serve your site from a domain you own, such as www.example.com. vmcog issues the HTTPS certificate for you.

  1. Enter the domain in the console. Use a subdomain like www; the bare domain is covered below.
  2. The console shows two records with a verification key unique to your site. Add both at your DNS provider:
    TypeNameValue
    CNAMEwwwcustomers.vmcog.com
    TXT_vmcog.wwwvmcog-verify=<your key>
  3. Click Check DNS. Records usually show up within minutes but can take an hour. Once checked, the site is live on your domain within a few minutes while the certificate is issued.

Most providers add your domain to the name for you, so type www and _vmcog.www, not the full name. If a www record already exists (often a parking page), edit or delete it first: a name can hold only one CNAME.

Cloudflare

Websites → your domain → DNS → Records → Add record. Add the CNAME and TXT. Set the CNAME's proxy status to DNS only (grey cloud).

GoDaddy

My Products → your domain → DNS → Add New Record. GoDaddy ships a www CNAME pointing to @; edit its value to customers.vmcog.com instead of adding another. Then add the TXT.

Namecheap

Domain List → Manage → Advanced DNS → Add New Record. Choose CNAME Record with host www, then TXT Record with host _vmcog.www. Delete any parking-page record for www.

Squarespace Domains (formerly Google Domains)

Domains → your domain → DNS → DNS Settings → Custom records → Add record. Add the CNAME with host www and the TXT with host _vmcog.www. Remove Squarespace's default www record if your domain has one.

Porkbun

Domain Management → your domain → DNS. Add the CNAME with host www and the TXT with host _vmcog.www. Remove the default ALIAS/CNAME parking records for www.

Amazon Route 53

Hosted zones → your domain → Create record. Record name www, type CNAME, value customers.vmcog.com. For the TXT, record name _vmcog.www, with the value in double quotes: "vmcog-verify=…".

The bare domain (example.com)

DNS does not allow a CNAME on the bare domain, so connect www and forward the bare domain to it: Cloudflare Rules → Redirect Rules, GoDaddy Forwarding, Namecheap Redirect Domain, Squarespace Domain forwarding, Porkbun URL Forwarding. Route 53 has no forwarding; use an S3 redirect bucket or move the bare domain's DNS elsewhere.

Disconnecting a domain in the console stops serving it right away. Changing to a new domain gives you a new key.

Analytics

In the console: Analytics on the site's card.

Every site gets built-in visitor analytics that only you can see: visitors, page views, time on page, scroll depth, referrers, campaigns (utm_source), countries, devices, browsers, the links and buttons people click, and JavaScript errors. Sites on T2 also see their database: tables and row counts, storage used, and API requests by kind with error rates.

vmcog adds one small script to your HTML pages to collect this (/_vmcog/a.js). It uses no cookies and stores nothing on visitors' devices; visitors are counted by a hash that changes every day, and visitors with Do Not Track or Global Privacy Control turned on are not counted. Data is kept for 13 months.

Forms: POST /_vmcog/f/:name no key

In the console: Forms on the site's card.

Any page on your site can collect form submissions, such as a contact or sign-up form, with no code and no key. Point the form at /_vmcog/f/ plus a name you choose (letters, digits, - and _, up to 40). It works on yoursite.vmcog.com and on your own domain.

<form action="/_vmcog/f/contact" method="post">
  <input name="name" required>
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <input name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <input type="hidden" name="_next" value="/thanks.html">
  <button>Send</button>
</form>
FieldDoes
any nameKept as sent. Up to 30 fields, names up to 64 characters, values up to 5000.
emailIf it is a valid address, emailed submissions use it as the reply-to, so you can just hit Reply.
_gotchaKeep it hidden. People never fill it in; spam bots do, and their submission is quietly dropped.
_nextOptional page on your site to show after sending, such as /thanks.html. Without it visitors see a short thank-you page.

A normal form post gets the thank-you page or a redirect to _next. From JavaScript, send JSON and you get { "ok": true } back:

await fetch("/_vmcog/f/contact", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, message }) });

Inbox or email: one or the other

Submissions go to exactly one place:

To switch to email, empty the inbox first (Delete all), then pick how to send and fill in the details, plus a from address and who to send to. vmcog signs in before saving, so a wrong setting is caught right away; Send test email checks delivery. The from address must be one your server or service lets you send from, usually on a domain you have verified with it.

Send withYou enter
Your own mail server (SMTP)Server, port (587, 465, 2525 or 25), username and password. The connection is always encrypted (TLS on 465, STARTTLS otherwise), and the server must be on the public internet.
Gmail / Google WorkspaceNothing to type: saving sends you to Google to allow sending email from your account. Gmail sends from that address or one of its "Send mail as" aliases.
Google Workspace service accountThe mailbox to send as and a service account JSON key with domain-wide delegation for the gmail.send scope, set up by your Workspace admin.
ResendAn API key. A sending-only key is enough.
PostmarkA server API token.
SendGridAn API key with mail send access.
MailgunYour sending domain (like mg.example.com), its region (US or EU) and an API key.

Passwords and API keys are stored encrypted and never shown again. Leave the field blank when changing other settings to keep the saved one; switching to a different service needs that service's key.

If your mail server is down or refuses a message, vmcog retries for about four hours (after 1, 2, 4 … 128 minutes). Messages it still cannot deliver are listed under Could not deliver with the error, where you can retry or delete them. Turning email off moves anything not yet delivered back to the inbox, so nothing is lost.

MCP server: POST /mcp secret key

In the console: Connect AI tab has these snippets ready to copy.

A Model Context Protocol server (Streamable HTTP) that lets an AI assistant build and edit your site. The secret key picks the site.

claude mcp add --transport http vmcog https://api.vmcog.com/mcp --header "Authorization: Bearer sk_…"
{ "mcpServers": { "vmcog": { "type": "http", "url": "https://api.vmcog.com/mcp",
  "headers": { "Authorization": "Bearer sk_…" } } } }
ToolDoes
list_tablesTables, their columns, and what visitors may do with each
run_sqlOne statement with optional params
run_scriptSeveral statements, atomically
set_accessAllow or deny visitors one operation on a table, with an optional row filter
list_files, read_file, write_file, delete_fileManage the published site

Tutorial: build a site in VS Code

From an empty folder to a live guestbook in about ten minutes, using GitHub Copilot's agent mode in VS Code. You never write code yourself.

  1. Create a site. Sign up, pick a name such as mysite, and copy the secret key (sk_…) somewhere safe. It is shown once; you can rotate it from the console later.
  2. Open a folder in VS Code. Any empty folder works. Make sure the GitHub Copilot Chat extension is installed and you are signed in.
  3. Add the vmcog server. Create .vscode/mcp.json in that folder. VS Code asks for the key the first time and stores it securely, so it never sits in the file:
    {
      "inputs": [
        { "type": "promptString", "id": "vmcog-key", "description": "vmcog secret key", "password": true }
      ],
      "servers": {
        "vmcog": {
          "type": "http",
          "url": "https://api.vmcog.com/mcp",
          "headers": { "Authorization": "Bearer ${input:vmcog-key}" }
        }
      }
    }
  4. Start the server. Click Start above "vmcog" in the file, paste your key when asked, and wait for the 8 tools to appear.
  5. Switch Chat to Agent mode. Open Chat (Ctrl+Alt+I, or ⌃+⌘+I on Mac) and choose Agent from the mode picker. The tools icon should list vmcog.
  6. Describe the site. For example:
    Build a guestbook on my vmcog site. Create an "entries" table with name, message
    and created_at. Let visitors read and add entries but not change or delete them.
    Write index.html with a form and the 50 newest entries, styled warm and simple.
    The assistant will call run_script to create the table, set_access to open it to visitors, and write_file to publish the page. Approve each tool call as it comes up, or choose Always allow for vmcog.
  7. Open your site. Visit https://mysite.vmcog.com and sign the guestbook. The entry is saved in your site's database; you can see it under Data in the console.
  8. Keep going. Ask for changes in plain words: "add a dark mode", "show the entry count at the top", "add an About page". The assistant reads the current files with read_file before changing them.

Tutorial: build a site with Claude Code

The same guestbook, built from the terminal with Claude Code.

  1. Create a site. Sign up, pick a name such as mysite, and copy the secret key (sk_…).
  2. Add the vmcog server. In any folder, run:
    claude mcp add --transport http vmcog https://api.vmcog.com/mcp --header "Authorization: Bearer sk_…"
    Add --scope user to make it available in every folder.
  3. Check the connection. Start claude and type /mcp. vmcog should show as connected with 8 tools.
  4. Describe the site. For example:
    Build a guestbook on my vmcog site. Create an "entries" table with name, message
    and created_at. Let visitors read and add entries but not change or delete them.
    Write index.html with a form and the 50 newest entries, styled warm and simple.
    Claude asks before each vmcog tool call the first time; choose Yes, and don't ask again to let it work through the whole build.
  5. Open your site. Visit https://mysite.vmcog.com and sign the guestbook.
  6. Keep going. Ask for changes in plain words: "add a dark mode", "add an About page".

Other AI tools

Any assistant that supports MCP works. Add vmcog with the snippet for your tool and restart it, then follow the same steps: check that the 8 tools appear, give it the guestbook prompt above and open your site.

Cursor

Add to ~/.cursor/mcp.json (every project) or .cursor/mcp.json (one project), then use Chat in Agent mode:

{ "mcpServers": { "vmcog": { "url": "https://api.vmcog.com/mcp",
  "headers": { "Authorization": "Bearer sk_…" } } } }

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json, then use Cascade:

{ "mcpServers": { "vmcog": { "serverUrl": "https://api.vmcog.com/mcp",
  "headers": { "Authorization": "Bearer sk_…" } } } }

Claude Desktop

Open Settings → Developer → Edit Config and add the server below. It needs Node.js installed to bridge the desktop app to vmcog:

{ "mcpServers": { "vmcog": { "command": "npx",
  "args": ["-y", "mcp-remote", "https://api.vmcog.com/mcp", "--header", "Authorization:Bearer sk_…"] } } }

Gemini CLI

Add to ~/.gemini/settings.json, then check with /mcp:

{ "mcpServers": { "vmcog": { "httpUrl": "https://api.vmcog.com/mcp",
  "headers": { "Authorization": "Bearer sk_…" } } } }

OpenAI Codex CLI

Add to ~/.codex/config.toml, with your key in the VMCOG_KEY environment variable:

[mcp_servers.vmcog]
url = "https://api.vmcog.com/mcp"
bearer_token_env_var = "VMCOG_KEY"

These files hold your secret key, so keep them out of anything you share or commit.

Limits & errors

LimitValue
Storage per site (data and files)100 MB
Request body1 MB; 8 MB for file uploads
Rows per response1000
Requests per site600 per minute
Form submissions10 per 10 minutes from one visitor; 32 KB each; 1000 in the inbox

Errors come back as { "error": "message" } with a status code:

StatusMeans
400Bad SQL, filter, column or body
401Missing or unknown key
403The public key tried something visitor access does not allow, or called a secret-key endpoint
404Unknown table, file or endpoint
413Body too large
415A form was sent as something other than a form post or JSON
429Rate limit hit; wait a minute