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
- Create a site. You get
yoursite.vmcog.comand a secret key, shown once. - 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.
- Prefer the terminal or an AI assistant? Everything below works with
curl,fetchor 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
| Key | Looks like | Use it for |
|---|---|---|
| Secret | sk_… | Everything: SQL, uploads, MCP. Keep it on your machine or server, never in a page. Rotate it from the console if it leaks. |
| Public | pk_… | 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:
| Body | Does | Returns |
|---|---|---|
{ "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.
| Method | Does | Body | Needs |
|---|---|---|---|
GET | Read rows | select rule | |
POST | Insert one row or an array of rows; returns them with their ids | { col: value } or [ … ] | insert rule |
PATCH | Update the rows matching the filters; returns them | { col: value } | update rule and at least one filter |
DELETE | Delete the rows matching the filters; returns them | delete rule and at least one filter |
Query string
| Parameter | Example | Meaning |
|---|---|---|
col=op.value | ?votes=gte.10 | Filter. op is eq, neq, gt, gte, lt, lte or like (% wildcard). Repeat to AND them. |
col=is.null | ?deleted_at=is.null | Also is.notnull. |
select | ?select=id,body | Columns to return. Defaults to all. |
order | ?order=created.desc,id | Sort by .asc or .desc; defaults to ascending. |
limit, offset | ?limit=20&offset=40 | Paging. 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.
| Column | Meaning |
|---|---|
tbl | Table name |
op | select, insert, update or delete |
filter | Optional 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.
| Method | Does |
|---|---|
PUT /site/:path | Create or replace a file. The request's Content-Type is served back to visitors. Returns { path, bytes }. |
DELETE /site/:path | Remove 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.
- Enter the domain in the console. Use a subdomain like
www; the bare domain is covered below. - The console shows two records with a verification key unique to your site. Add both at your DNS provider:
Type Name Value CNAME wwwcustomers.vmcog.comTXT _vmcog.wwwvmcog-verify=<your key> - 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>
| Field | Does |
|---|---|
| any name | Kept as sent. Up to 30 fields, names up to 64 characters, values up to 5000. |
email | If it is a valid address, emailed submissions use it as the reply-to, so you can just hit Reply. |
_gotcha | Keep it hidden. People never fill it in; spam bots do, and their submission is quietly dropped. |
_next | Optional 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:
- Inbox (the default): kept in the console's Forms page, where you can read and delete them. It holds up to 1000; once full, new submissions are refused until you delete some.
- Email: sent through your own mail server (SMTP), Gmail / Google Workspace, or an email service (Resend, Postmark, SendGrid or Mailgun) to up to 5 addresses, and not kept once delivered. The inbox is turned off.
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 with | You 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 Workspace | Nothing 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 account | The 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. |
| Resend | An API key. A sending-only key is enough. |
| Postmark | A server API token. |
| SendGrid | An API key with mail send access. |
| Mailgun | Your 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_…" } } } }
| Tool | Does |
|---|---|
list_tables | Tables, their columns, and what visitors may do with each |
run_sql | One statement with optional params |
run_script | Several statements, atomically |
set_access | Allow or deny visitors one operation on a table, with an optional row filter |
list_files, read_file, write_file, delete_file | Manage 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.
- 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. - Open a folder in VS Code. Any empty folder works. Make sure the GitHub Copilot Chat extension is installed and you are signed in.
- Add the vmcog server. Create
.vscode/mcp.jsonin 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}" } } } } - Start the server. Click Start above
"vmcog"in the file, paste your key when asked, and wait for the 8 tools to appear. - 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.
- Describe the site. For example:
The assistant will callBuild 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.run_scriptto create the table,set_accessto open it to visitors, andwrite_fileto publish the page. Approve each tool call as it comes up, or choose Always allow for vmcog. - Open your site. Visit
https://mysite.vmcog.comand sign the guestbook. The entry is saved in your site's database; you can see it under Data in the console. - 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_filebefore changing them.
Tutorial: build a site with Claude Code
The same guestbook, built from the terminal with Claude Code.
- Create a site. Sign up, pick a name such as
mysite, and copy the secret key (sk_…). - Add the vmcog server. In any folder, run:
Addclaude mcp add --transport http vmcog https://api.vmcog.com/mcp --header "Authorization: Bearer sk_…"--scope userto make it available in every folder. - Check the connection. Start
claudeand type/mcp. vmcog should show as connected with 8 tools. - Describe the site. For example:
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.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. - Open your site. Visit
https://mysite.vmcog.comand sign the guestbook. - 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
| Limit | Value |
|---|---|
| Storage per site (data and files) | 100 MB |
| Request body | 1 MB; 8 MB for file uploads |
| Rows per response | 1000 |
| Requests per site | 600 per minute |
| Form submissions | 10 per 10 minutes from one visitor; 32 KB each; 1000 in the inbox |
Errors come back as { "error": "message" } with a status code:
| Status | Means |
|---|---|
400 | Bad SQL, filter, column or body |
401 | Missing or unknown key |
403 | The public key tried something visitor access does not allow, or called a secret-key endpoint |
404 | Unknown table, file or endpoint |
413 | Body too large |
415 | A form was sent as something other than a form post or JSON |
429 | Rate limit hit; wait a minute |