REST API
Work with sites, files, revisions, domains, and bandwidth from your own tools.
Read as MarkdownOn this page
Publish a site, make changes, and roll back from your own tools. The REST API is available now at https://app.ironfountain.com/api/hosting/v1.
For ChatGPT or Claude on the web, start with Connect ChatGPT or Claude. This page covers direct REST requests from scripts and other HTTP clients.
The product namespace is /api/hosting/v1. Iron Fountain sign-in and organizations stay shared. Existing /api/v1 requests continue to reach the same Hosting handlers without a redirect; new integrations should use the product URL. Keys grant only their listed Hosting permissions and do not grant access to future products.
Quick start
Create an API key in your organization’s API keys screen. Keys default to all permissions and never expire; you can narrow permissions, choose an expiry, or revoke a key there. Each key belongs to one organization and follows its creator’s membership.
Store the key in your tool’s secret settings or a terminal environment variable named IRONFOUNTAIN_API_KEY. Keep it out of published website files and browser JavaScript.
curl https://app.ironfountain.com/api/hosting/v1/me \
-H "Authorization: Bearer $IRONFOUNTAIN_API_KEY"
The response includes your organization in accounts. Its id is the accountId used in API paths. The API uses accounts and account_id for what the app calls organizations.
curl https://app.ironfountain.com/api/hosting/v1/accounts/ACCOUNT_ID/sites \
-H "Authorization: Bearer $IRONFOUNTAIN_API_KEY"
Test sites are temporary imports, with a one-hour lifetime. Saving one to an organization retires its test URL immediately. Staging is a saved site’s permanent place to review changes. Production is the published site. Revision links open exact saved revisions and follow history retention. The existing preview_url field refers only to a temporary test site; do not call it staging.
Site lists and details return production_url (null before publication), staging_url, and staging_revision_id. Staging has a permanent address, such as my-site.staging.ironfountain.com, showing its current revision; it does not imply publication. Each retained revision also has its own immutable revision_url, including in save and restore responses. Registered sites never use temporary test-site URLs.
Replace ACCOUNT_ID and SITE_ID in examples with the IDs returned by the API. Requests that send JSON require Content-Type: application/json.
Permissions
| Permission | What it allows |
|---|---|
sites:read | Read sites, files, revision history, import reports, DNS status, and storage usage. |
sites:write | Create sites, change site settings, and manage hostname-specific file or path redirects. |
deployments:write | Import, upload, publish, restore, pin/unpin, delete revisions or snapshots, and edit redirects saved with revisions. |
domains:write | Prepare and verify custom domains and check their connection. |
Permissions are independent. A tool that reads the current files and publishes changes needs both sites:read and deployments:write. Organization and member administration, site transfers, passwords, and API key management use signed-in dashboard sessions.
Create or read a site
Create a site with POST /accounts/ACCOUNT_ID/sites:
{"name":"My website","slug":"my-website"}
The optional slug gives the site its free address, such as my-website.ironfountain.com. Omit it to choose an available address automatically from the name. Site names can contain spaces; explicit slugs use 3–40 letters, numbers, and single hyphens between words. Sites serve static files, directory indexes, and an optional 404.html for missing pages.
GET /accounts/ACCOUNT_ID/sites/SITE_ID returns site, domains, deployments, revision_history, pinned_revisions, snapshots, redirects, and revision_redirects. site.active_deployment_id identifies the published revision, or is null before the first publication.
Edit and publish safely
First read the current site, then its revision manifest with GET /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID. Read each file with GET /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID/file?path=index.html. File responses contain base64-encoded content; decode it before editing.
Send the complete updated file set to POST /accounts/ACCOUNT_ID/sites/SITE_ID/deployments. Files omitted from the upload are absent in that revision.
{
"label": "Update homepage copy",
"expected_deployment_id": "CURRENT_REVISION_ID",
"publish": true,
"files": [
{"path": "index.html", "content": "<!doctype html><h1>Welcome</h1>", "encoding": "utf8"},
{"path": "styles.css", "content": "body { color: #234; }", "encoding": "utf8"}
]
}
Save that JSON as revision.json, then send it:
curl https://app.ironfountain.com/api/hosting/v1/accounts/ACCOUNT_ID/sites/SITE_ID/deployments \
-H "Authorization: Bearer $IRONFOUNTAIN_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @revision.json
Use expected_deployment_id: null for a new site. Supplying the current revision ID makes a stale update fail with 409 instead of overwriting someone else’s changes. Read the latest files and reconcile the edits before retrying. publish: false updates staging without changing production. Include expected_staging_deployment_id to reject concurrent staging edits. Production publication and rollback leave staging unchanged; a first direct publication initializes staging if it is empty. Renaming staging requires sites:write, returns 409 for a taken name, and accepts a valid DNS label of 1–63 characters. Site details include separate staging and production history.
Include index.html at the root. Use unique relative paths and encoding: "base64" for binary files. A JSON upload accepts up to 20,000 files, 100 MiB of supplied content, and 5 MiB per supplied file. The complete site can contain 20,000 files and 1 GiB. To edit a larger imported site, pass base_deployment_id with the retained revision ID and supply only changed files. Unchanged files are reused on the server. Optional deleted_paths removes files from that base; paths cannot be both changed and deleted. The base must belong to this site. Unchanged files share storage between revisions; text is compressed. Publication queues CDN invalidation, so cached pages may take a moment to update.
History and rollback
| Operation | Request |
|---|---|
| List saved revisions | GET /accounts/ACCOUNT_ID/sites/SITE_ID |
| Read a revision manifest | GET /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID |
| Publish or restore a saved revision | POST /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID/restore with {} |
| Revert staging | POST /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID/stage with {"expected_deployment_id":"CURRENT_STAGING_ID"} |
| Rename staging | PATCH /accounts/ACCOUNT_ID/sites/SITE_ID/staging with {"slug":"secret"} |
| Pin a revision | PATCH /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID with {"pinned":true} |
| Unpin a revision | The same request with {"pinned":false} |
| Read storage usage | GET /accounts/ACCOUNT_ID/sites/SITE_ID/storage |
History is automatic, with separate allowances for recent revisions and pins:
| Plan | Recent revisions per environment | Pinned revisions per site |
|---|---|---|
| Spring | 3 | 0 |
| Flow | 20 | 10 |
| Cascade | 50 | 25 |
site.history_policy returns plan_name, keep_per_environment, pin_limit, can_pin, monthly_months, and session_days. Pinning does not reduce recent history. Pins are shared across the site's two environments. Spring rejects pin requests with 403; a full pin allowance returns 409. Unpinning remains available. Pinning an already pinned revision does not use another slot.
Flow and Cascade also have monthly snapshot pointers lasting 12 months, plus starting snapshots lasting 30 days when changes resume after at least 24 hours of inactivity in that environment. Each snapshot includes snapshot_id, target revision id, channel, kind (monthly or before_edit), as_of, expires_at, revision created_at, and revision_url. Monthly pointers use the revision that was current at the start of the calendar month (UTC), even if it was created years earlier. Several snapshots can share one revision without copying files. Restore a snapshot using its target revision id with the existing stage or restore endpoint.
| Action | Request |
|---|---|
| Delete a snapshot pointer | DELETE /accounts/ACCOUNT_ID/sites/SITE_ID/snapshots/SNAPSHOT_ID |
| Delete an unused revision | DELETE /accounts/ACCOUNT_ID/sites/SITE_ID/deployments/REVISION_ID |
Both require deployments:write. Deleting a snapshot removes only its pointer and it will not reappear. Revision deletion returns 409 while current in staging or production, pinned, referenced by any retained snapshot, uploading, or used by an unexpired test site. Current revisions and all snapshot targets are protected from automatic cleanup. Shared files are removed only after no retained revision uses them, with a one-hour safety window after creation or replacement.
If a paid downgrade reduces the pin allowance, existing pins stay protected but no additional pins can be added until usage falls below the new cap. When paid access ends, Spring's history applies: pins and snapshots end, and older revisions become eligible for cleanup. See automatic history and snapshots for details.
Import an existing website
Use GET /imports/lookup?inspect_redirects=true&url=https%3A%2F%2Fexample.com before creating another site. It can return action: "new", "resume", or "manage". An existing result includes a browser href; authorized results may include site_id and account_id. Redirect-only addresses leading to a recognized site open that existing site instead of copying it again. Looking up a redirect does not connect or verify its domain.
For a new import, create a site and send POST /accounts/ACCOUNT_ID/sites/SITE_ID/imports:
{
"url": "https://example.com/",
"rights_confirmed": true,
"terms_version": "2026-09-11"
}
Importing requires permission from the content owner. Read the current Terms and GET /legal/terms for the current version and declaration before affirming it. Every new import and refresh requires this agreement.
An already connected site rejects new imports with 409 before fetching the source. Import into an empty site; an unfinished migration can explicitly refresh its same source with refresh: true and expected_import_id.
Optional extra_paths lists up to 100 paths, for example ["/downloads/guide.pdf", "/old-page"], for files and redirects absent from links and sitemaps. The dashboard and test forms place this under Advanced import options. The same crawl limits and public-network checks apply. Omitting extra_paths during refresh preserves the previous list; sending [] clears it. Progress responses include stage and, during saving, report.saving.saved_files and report.saving.total_files.
A new job returns 202 with an import id. An already recognized site returns 200 with a resume or manage action. Use GET /accounts/ACCOUNT_ID/sites/SITE_ID/imports to poll progress and obtain the compatibility report, permanent staging_url, and revision_url for the exact imported revision. Import limits are 200 pages, 20,000 files, 1 GiB total, 100 MiB per file, and fifteen minutes. Paid sites can start ten imports per site per hour, with up to two imports in progress per organization. Free imports also have a shared ten-import hourly allowance. Imports inspect public static content; test interactive features in the preview.
Temporary test sites expire after one hour or immediately when saved to an organization. Registered sites have a permanent staging_url plus exact revision_url links. Staging does not have a one-hour deadline; retained revision links remain available while that revision is kept. Both are excluded from indexing. Test the copy and review the report before publishing with POST /accounts/ACCOUNT_ID/sites/SITE_ID/imports/IMPORT_ID/publish and {"reviewed":true}. Blocking findings must be resolved before publication.
To refresh a saved site while its custom domain remains unverified, send another import request with refresh: true and expected_import_id set to the latest import ID. A refresh stages changes on the same site and preserves published files until reviewed and published. Identical content reuses the prior revision. Once verified, edit through file deployments instead of reimporting the hosted website.
Wix and Squarespace imports are not supported yet. Detected sites receive a blocking unsupported_wix or unsupported_squarespace finding, a failed import job, and no new revision. Detection on the first page stops before asset downloads. A rejected refresh preserves existing staging and production. Webflow and other public static websites remain eligible, subject to the usual compatibility and size checks.
Import findings retain level, code, message, and the first affected url. Repeated matching explanations may also include count (distinct affected resources) and urls (up to 24 examples); missing count means one. Different messages, including different source HTTP statuses, remain separate groups. Reports retain up to 150 explanation groups and may include omitted_findings if further explanations could not be retained. Form-handler review entries include next_step; use that guidance and the page link when helping an owner resolve an import issue. An external_script_assets finding means a preserved external script references builder-hosted files that the importer cannot rewrite inside that external service.
Single-page applications (SPAs) are not supported yet. Detected client-rendered app shells or client-side routing produce a blocking unsupported_spa finding and no new staging revision. Detection does not execute JavaScript; a successful static scan still requires reviewing the copy. Ordinary JavaScript menus, forms, and animations are not rejected just for using JavaScript.
Custom domains and HTTPS
Custom domains currently support a root domain and its www address as a pair. Either can be the main address; the other redirects automatically with paths and query strings preserved.
Prepare the pair with POST /accounts/ACCOUNT_ID/sites/SITE_ID/domain-setup:
{"hostname":"example.com","primary":"www"}
Read GET /accounts/ACCOUNT_ID/sites/SITE_ID/dns for the root certificate TXT challenge, ownership, and connection state. Add all records returned in certificates; read the supplied names and values rather than constructing them. Iron Fountain obtains the certificates directly from Let’s Encrypt. The fresh root challenge normally also proves ownership of the pair. ownership.method is pending, acme, verified, or txt when an additional fresh ownership record is needed. Checks run automatically; connection.next_check_at is the next check in Unix milliseconds. Repeated requests do not bypass the schedule.
Keep the old website’s A/CNAME records until connection.dns_switch_ready is true: ownership and trusted root HTTPS have passed. Then use gateway_ip for the root A record and cname_target for the www CNAME. Both addresses resolve to Iron Fountain’s gateway; website URLs remain unchanged. connection.phase: "active" means DNS and HTTPS passed and the primary address is active. The www certificate is issued automatically over HTTP after its CNAME resolves to our gateway; no www TXT record is needed. www HTTPS may be briefly unavailable during issuance. Keep old hosting available while DNS caches expire. DNS routing alone never verifies ownership. Certificates renew automatically through HTTP after cutover, without recurring TXT updates. connection.certificate_status reports each hostname’s expiry, renewal schedule, retry time, and any renewal error.
Additional redirect domains
In the dashboard, open a site → Domains → Add redirect domain. Enter another root domain, such as example.net. Both example.net and www.example.net redirect to the existing site’s main address. No second website or import is created. A site supports up to 20 additional root/www pairs.
The API requires domains:write:
POST /api/hosting/v1/accounts/ACCOUNT_ID/sites/SITE_ID/redirect-domains
Content-Type: application/json
{"hostname":"example.net"}
Publish the site and connect its main custom domain first. Repeating this request resumes the same connection without replacing its ownership token or restarting the check countdown. Read redirect_connections from GET …/dns for each root’s status, certificate challenges, next_check_at, dns_switch_ready, and hosting_ready. Each redirect connection includes its own ownership instructions. Its fresh root certificate TXT verifies ownership of that pair; every additional domain needs its own proof.
Follow the same order as the main domain: add the displayed root certificate TXT record, then the root A and www CNAME after ownership and root HTTPS pass. www HTTPS is issued automatically after its CNAME points here; no www TXT record is needed. Use that redirect connection’s cname_target and the shared gateway_ip. Keep existing website DNS records until dns_switch_ready is true. Each pair progresses and renews independently.
Connected redirect domains return HTTP 308 to the site’s current main hostname, preserving the path and query string. They follow later main-address changes. They cannot be selected as the primary hostname or have individual path redirects.
Remove a pair with DELETE …/redirect-domains/example.net. HTTP 202 means removal is queued. The entry remains phase: "removing" until its Bunny hostnames are detached; failures retry automatically while the names stay reserved to prevent reassignment during cleanup. Both root and www are removed together. The main domain, site content, and revision history remain attached.
File and path redirects
Send POST /accounts/ACCOUNT_ID/sites/SITE_ID/redirects with the ID of an attached hostname:
{
"domain_id": "DOMAIN_ID",
"source_path": "/old-page",
"destination": "/new-page",
"status_code": 301,
"preserve_query": true
}
Rules match an exact path on that hostname. Destinations can be a local path or an external HTTPS URL. Status codes are 301 or 302. Edit a rule with PATCH /accounts/ACCOUNT_ID/sites/SITE_ID/redirects/REDIRECT_ID, sending the same fields as creation. Remove it with DELETE at that address. Whole extra-domain redirects are not automatically created by importing a redirect address.
Claude, ChatGPT, and other tools
Use your own AI account with a tool that can make authenticated HTTP requests. Give it this reference, the organization and site IDs, and access to the API key through its secret settings. Ask it to read the current revision, preserve every unchanged file, make your requested edits, and publish with expected_deployment_id. Every published revision remains a rollback point while retained.
For ChatGPT or Claude on the web, use the hosted MCP connector at https://app.ironfountain.com/mcp/hosting. It uses Iron Fountain sign-in and OAuth access to the organizations you approve instead of a pasted API key. See Connect ChatGPT or Claude for setup and the tool reference. An organization owner may need to enable the connector in Claude; code execution egress settings do not govern enabled MCP connections. Other HTTP tools can continue using the REST API above.
Redirects in revisions
The site detail response includes revision_redirects for the current production and staging revisions, alongside the hostname-specific redirects list. Each revision entry contains environment, revision_id, and redirects (from, to, status). Rollback changes this list to the selected revision’s rules. The dashboard lists production and staging separately, each with its own Edit and Remove controls. Editing or removing a rule saves a new revision only in the selected environment, preserving its other files and leaving the other environment unchanged. The old revision remains available for rollback. API clients can do the same using the existing deployments endpoint: choose that environment’s revision as base_deployment_id, replace __ironfountain/redirects.json with the remaining rules (or delete that file via deleted_paths when none remain), set publish for the selected environment, and include its expected_deployment_id or expected_staging_deployment_id to reject stale edits.
The importer preserves discovered path redirects, including those found through extra_paths. It cannot discover unlinked paths that were not supplied. Root/www normalization remains part of domain setup. Local redirect destinations stay on staging or the current production hostname; external destinations remain external. External redirect targets are not crawled as new websites.
Redirect definitions are retained in __ironfountain/redirects.json. API and MCP file editing can manage this file with the other revision files:
{"version":1,"redirects":[{"from":"/old-page","to":"/new-page/","status":301},{"from":"/downloads/current.pdf","to":"https://files.example.com/current.pdf","status":302}]}
These rules apply to that revision in test sites, staging, revision URLs and production. Publishing and rollback switch content and redirects together. Rules use exact paths (with an optional exact query string), support 301/302/303/307/308, and cannot contain redirect loops. Queries are not automatically forwarded. The file allows up to 2,000 rules and 2 MiB. Existing hostname-specific rules configured through /redirects take precedence. Normal root/www and secondary-domain redirects still run first.
Errors
Errors return JSON containing error. Common statuses are 400 for invalid input, 401 for missing or invalid authentication, 403 for insufficient permission, 404 for unavailable resources, 409 for a conflict or an already-hosted source, 413 for size limits, and 429 for request or import limits.
A website already hosted by an organization you cannot access returns 409 with code: "already_hosted" and does not expose that organization’s identity. Sign in with the appropriate login to manage it. Organization IDs, site IDs, and revision IDs stay stable; a site transfer changes which organization’s keys may access it.
For concurrency-safe publication or rollback through the restore endpoint, send {"expected_deployment_id":"CURRENT_LIVE_REVISION_ID"} (or null for an unpublished site). A mismatch returns 409. Omitting the field preserves the earlier restore behavior.
Import compatibility and download limits
The importer evaluates whether public page output can be captured as a usable static snapshot. A database or server-rendered source alone does not disqualify a site. Public HTML from server-style routes and finite query URLs can be saved; query pages receive static paths and captured links are rewritten. Incoming query URLs from outside the copy need migration review because they do not automatically map to those new paths.
Navigation and sitemap discovery stop as soon as more than 200 distinct page URLs are found, before downloading the remaining pages and assets. Sitemap discovery reads up to twenty files, each limited to 2 MB. Account pages discovered after the public starting page are skipped, including login, registration, and password reset. report.skipped_pages lists their URLs and reasons; the import saves the remaining site with a needs_review verdict. Skipped pages and redirect aliases are removed from copied sitemap entries, and links to them are flagged for review. An account/login starting page, unsupported SPA, exceeded budget, or incompatible resource content remains a blocker. Secondary pages/files returning an HTTP error are omitted and listed as missing_resource warnings. This gives complete: false with verdict: needs_review; a reviewed copy can still be published.
Forms, runtime network calls, and embedded services are flagged for review. They do not by themselves prove that the visible page cannot be hosted statically. Test which behaviors need a live service, user state, new searches, transactions, or fresh data before publishing. A public crawl cannot establish that every hidden dependency is absent; this importer parses fetched HTML and does not run a headless browser.
Every crawl is bounded by 1.25 GiB of downloaded response bodies, 25,000 fetch operations, 200 HTML pages, 20,000 saved files, 100 MiB per file, 1 GiB per imported revision, and fifteen minutes. Redirect hops and headers can add a small amount of network traffic beyond the response-body allowance. Streams are aborted when limits are exceeded.
Site bandwidth
GET /api/hosting/v1/accounts/ACCOUNT_ID/sites/SITE_ID/bandwidth?month=YYYY-MM requires sites:read. The month is optional and defaults to the current UTC month. The response includes monthly totals, daily rows grouped by hostname and source, and sources showing collection timestamps and errors. Site list responses also include month_bandwidth_bytes and month_bandwidth_requests for dashboard cards.
CDN usage includes response headers and bodies served at the edge, including cache hits. Direct gateway usage records response body bytes written by Caddy. Origin fetches are excluded to avoid counting CDN delivery twice. These measurements differ from a provider invoice: transport overhead, other service traffic, and differing byte definitions are not included in a single billing total.
Collection runs every five minutes and counts only complete windows ending at least ten minutes ago. Graphs normally trail traffic by ten to fifteen minutes. Recent windows are rechecked for late logs; their totals are replaced without counting the same window twice. The usage database stores aggregate counters, with one hour of recent window totals and 400 days of daily totals. It stores no per-request records, visitor IPs, request paths, cookies, or authorization values. Catch-up work and provider delays can increase the lag; logs arriving outside the recent recheck window may be missed. If collection is offline longer than provider logs remain, missing traffic cannot be recovered. The API’s sources metadata reports collection progress and errors.
Staging and temporary addresses block indexing. Free production addresses retain their own robots rules; once the primary custom domain is active, the free address redirects to it instead of serving an indexable mirror.
Forms and submissions
Forms work with ordinary HTML and any site builder. Compatible forms are detected during import and connected to Iron Fountain. See the Forms guide for HTML examples, manifest authoring, custom browser clients, and dedicated MCP tools. Form definitions are stored with each immutable revision in __ironfountain/forms.json. Publishing or rolling back changes the form definitions with the site. Saved submissions remain independent of revision retention. Custom third-party form actions remain configured with their original service and require review.
Each submission stores its fields as a JSON object. Multiple selections use arrays. Indexed site ID, form key, environment, status, and timestamp support the inbox without requiring a new database schema for each form. Attachments are private Bunny Storage objects and can only be downloaded by someone with access to the site.
All routes below are relative to /api/hosting/v1/accounts/:accountId/sites/:siteId.
| Method | Route | Permission | Behavior |
|---|---|---|---|
| GET | /forms | sites:read | List forms, current revision availability, per-environment form_handlers, unread counts, and usage. |
| POST | /forms/connect | deployments:write | Connect recognized or explicitly opted-in HTML forms, synchronize fields, and refresh the form handler review in staging. Optional body: expected_staging_revision_id for concurrency. Does not recrawl or publish. |
| PATCH | /forms/:formKey | sites:write | Set enabled to pause or resume a form. |
| GET | /form-submissions | sites:read | List up to 50 submissions, their fields, and attachment metadata. |
| PATCH | /form-submissions/:id | sites:write | Set read (boolean) to mark read or unread. |
| DELETE | /form-submissions/:id | sites:write | Delete a submission and revoke attachment access immediately; private files are cleaned up in the background. |
| GET | /form-attachments/:id | sites:read | Download an attachment as a file. Authentication is required. |
| GET | /form-submissions.csv?form=:formKey | sites:read | Download all matching submissions for one form. Each field has a column, with original JSON included to preserve older field formats. |
List and CSV filters: form, environment=production|test, unread=true, and q to search field values. Lists return a next cursor; pass it as before for the next page.
Staging and saved revision addresses save to the Staging submissions inbox. Temporary test sites simulate submissions and file selection without storing submitted data. Staging and test sites remain blocked from indexing. Production submissions use the Production inbox. Forms share the site's organization membership, including after a site transfer.
A form supports up to 100 fields and 20 attachments, with 10 MB per file and supported document, image, audio, video, and ZIP extensions. The site has a 10 GB attachment allowance and room for 10,000 saved submissions; submissions are retained until deleted. JSON submissions are limited to 64 KB. Rate limits apply across all containers: 20 submission attempts per visitor per site per 10 minutes and 2,000 per site per day. Abandoned uploads expire after an hour.
Each environment has one inbox. Honeypot checks and request limits are automatic; there is no CAPTCHA configuration or separate spam classification. HTML field patterns and browser-native validation remain in the page; the server also validates required fields, email, URL, numeric ranges, selected options, attachment ownership, and request size. Email notifications are not enabled. Historical submissions and private notification settings cannot be discovered by crawling a public site.
Imported pages submit to same-origin /__ironfountain/forms/:formKey/submit using the bundled form script. The script obtains a short-lived signed session and uploads each attachment separately before submitting its IDs. Never put an account API key in a public form. Standard API and MCP file editing can update the versioned form manifest and HTML together.
Organization billing
The dashboard’s Billing navigation lists every site plan, the organization’s shared card, and its invoices. Choose Manage plan for a site, then Change plan. Plans are per site; the saved card is shared across the entire organization.
GET /accounts/ACCOUNT_ID/billing returns sites with their billing state and profile with the shared payment method summary and recent invoices. It requires an owner browser session, as do payment changes. API keys and MCP connections cannot manage billing.
An owner of an already complimentary site can choose Flow or Cascade allowances without payment using POST /accounts/ACCOUNT_ID/sites/SITE_ID/billing/complimentary-plan with {"plan_id":"plus-50"}. It preserves complimentary status and creates no Stripe customer, subscription, or charge. Non-complimentary sites cannot use this endpoint. Bandwidth increases take effect immediately; a reduction preserves the allowance already granted for the current calendar month. History follows the selected plan.
Form handler detection preserves explicit external destinations. Current-server and matching-provider dependencies, and unrecognized submission code, are reported for review instead of automatically connected. See the forms guide for provider coverage and the handlers metadata.
Import addresses must be a valid root domain or its www address, with an optional http:// or https:// prefix and trailing slash. Other subdomains, paths, query strings, credentials, ports, IP addresses, and malformed hostnames are rejected before source inspection or import creation. This checks address syntax; DNS and site availability are checked afterward.