Publish on your own domain

Embed
Preview of the card
<a href="https://forumulate.com/guides/custom-domains"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/b4cea5cb-e8a5-43a3-b03c-46180be37fcd/social-card.svg?v=3e7ae33eec8b" alt="Publish on your own domain" width="600" height="315"></a>

Your documentation answers at docs.yourcompany.com — your domain, your canonical URLs, your sitemap. Search engines credit you, not us.

This is the one page for custom domains. Two earlier pages covered the same ground and disagreed with each other about the record type, the proxy target and where the panel lives; this replaces both.

Before you start

  • A space that is published and public.

  • Access to your domain's DNS, or the ability to put one file on the site.

  • The console panel: open the space, go to the Settings fold, and find the Address group with its Custom domain panel. Over the API, every step below is a call under /orgs/{orgId}/spaces/{spaceId}/domains.

Pick one of three shapes

Shape

Address

DNS you create

TLS

A subdomain we own

yourname.forumulate.com

none

ours, instantly

A whole hostname of yours

docs.yourcompany.com or yourcompany.com

one routing record

ours, issued automatically

A path of a site you already run

yourcompany.com/docs

none

yours — TLS terminates at your proxy

The third shape is the one to choose if you want the links your documentation earns to build yourcompany.com itself. A subdomain is a separate site as far as a search engine's domain-level signals go, so docs.yourcompany.com builds docs.yourcompany.com.

A subdomain we own

Use Claim subdomain (POST …/domains/subdomain with {"subdomain": "acme"}). Because we own the parent domain it verifies instantly, there is no DNS step, and the certificate is ours. A handful of labels — www, api, app, admin, docs, status and similar — are reserved.

A whole hostname of yours

1. Add it

In Add domain, enter the hostname and leave basePath empty.

POST /orgs/{orgId}/spaces/{spaceId}/domains
  { "hostname": "docs.yourcompany.com" }

2. Prove you own it — any one of three proofs

The panel shows all three. You need only one, and proving a name once also proves everything under it, so a second subdomain of a domain you have already verified is trusted automatically.

Proof

What to do

Field in the API response

DNS TXT

Add a TXT record named _forumulate.docs.yourcompany.com with the value forumulate-verify=<token>.

verificationRecord — { name, value }

A file

Serve the shown text at https://docs.yourcompany.com/.well-known/forumulate-verify/<token>.

verificationFile — { path, contents }

A meta tag

Put the shown <meta> tag on the domain's home page.

verificationMeta — { tag }

The file proof is the easy one when DNS is not yours to edit. The meta tag is there for the case where neither DNS nor /.well-known is yours — a domain fronted by someone else's service.

3. Create the routing record we show you

This is the record the earlier pages got wrong. The record type depends on whether the hostname is an apex:

Your hostname

Record

Points at

docs.yourcompany.com (a subdomain)

CNAME

our edge host

yourcompany.com (an apex)

A

our edge IP

A subdomain gets a CNAME so our address can change without you re-pointing anything. An apex cannot hold a CNAME, so it gets an A record and you accept that an IP change means an edit. Do not copy a value out of a guide — read it from the panel, or from the API:

GET /orgs/{orgId}/spaces/{spaceId}/domains
# → domain.dnsTarget = { "type": "CNAME" | "A", "name": …, "value": … }

dnsTarget is null only on a path-mounted domain, which needs no DNS record at all, and on a deployment that pins no edge address.

Counting the records: two for a whole hostname if you verify by TXT (the TXT proof and the routing record), one if you verify by file or meta tag.

4. Verify, then activate

These are two separate steps, and conflating them is how a cutover goes wrong.

POST /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/verify
  { "activate": false }      # prove ownership, change nothing about traffic

POST /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/activate
  # now we answer this address

verified means we have proof you own the name. active means we answer requests for it. Verifying with {"activate": false} lets you prepare everything — proof, routing record, a last look at the content — while your own application keeps serving the address. POST …/activate is the moment of cutover. POST …/verify with no body activates in the same call, which is what you want for a brand-new hostname that nothing is serving yet.

A verify that finds no proof answers 400 (request.malformed) and says what each method saw, in details:

{
  "code": "request.malformed",
  "message": "Not verified — no proof found for docs.yourcompany.com: TXT: …; file: …; meta tag: ….",
  "details": {
    "txt": [
      "TXT: _forumulate.docs.yourcompany.com has 1 forumulate-verify value(s) but none is forumulate-verify=<token> (value mismatch — found \"forumulate-verify=old\")"
    ],
    "file": [
      "file: https://docs.yourcompany.com/.well-known/forumulate-verify/<token> answered 404"
    ],
    "meta": [
      "meta tag: https://docs.yourcompany.com/ answered 200 but did not contain the expected value"
    ]
  }
}

A success is always a 200 with verified: true, so the status code alone tells you which happened.

A certificate is issued for you through Let's Encrypt once the name is verified and your DNS points here. You never manage certificates for a whole-host domain.

If you run an ACME client of your own for one of your hostnames, the platform can answer its HTTP-01 challenge. Register the challenge with POST /orgs/{orgId}/spaces/{spaceId}/domains/acme-challenge and { "hostname", "token", "keyAuthorization" } (space.update; the hostname must be one of this organization's domains, or it is a 404). The certificate authority then fetches GET /.well-known/acme-challenge/{token} on that hostname, and the platform answers with the key authorization as plain text for the next hour, or a 404 for an unknown or expired token.

5. Ask the product whether it worked

GET /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/setup

This is the check to run instead of guessing. It answers with a mode, a boolean served, and a one-line message saying what to do next.

mode

What it means

verified

Already reaching our edge. Nothing to do.

dns

A whole host that must point at us. dns.found lists what it resolves to today; dns.reachesUs is false.

pending

Either verified but not activated (your own application still serves it — call activate), or activated moments ago and routing is still settling. Read message for which.

path

An activated subfolder that still does not reach us. forward carries the one proxy rule to add.

unverified

A subfolder whose ownership is not proven yet.

setup also returns crawlerBlocked. The probe sends an AI crawler's User-Agent (GPTBot), so true means a CDN in front of your domain refuses AI crawlers — usually a "block AI bots" rule — and AI assistants cannot fetch your pages. It says nothing about search engines, which such rules normally leave alone.

A domain that publishes a site has the same check at GET /orgs/{orgId}/sites/{siteId}/domains/{domainId}/setup, with the same answer shape.

The www. twin

A domain is registered for one hostname. yourcompany.com/docs is routed to us; www.yourcompany.com/docs is not, and reaches whatever else answers that name — usually your own application, which has no such page. setup looks at the twin every time and reports it:

  • wwwNotCovered: true — www.<host> resolves but does not reach this address.

  • www — what we found: { hostname, resolves, covered, how, detail }, where how is redirect-by-us, registered, redirects-here (your own app already redirects it to this address), served-by-us, not-resolving or not-covered.

Two ways to cover it: redirect www in your own application, or have our proxy do it —

PUT /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/www
  { "alsoRedirectWww": true }
# or, for a site's domain: PUT /orgs/{orgId}/sites/{siteId}/domains/{domainId}/www

alsoRedirectWww is also accepted when you add the domain. With it on, our proxy answers www.<host> at the same path with a permanent redirect straight to https://<host>/… (one hop). Point the www name's DNS where the bare name points; for a whole-host domain we also request a certificate for it. It is refused (422) on a hostname that already starts with www..

A path of a site you already run

1. Add it with a basePath

POST /orgs/{orgId}/spaces/{spaceId}/domains
  { "hostname": "yourcompany.com", "basePath": "/docs" }

One hostname can carry several mounts — documentation at /docs, a community at /community — and each answers only under its own path. Publishing several spaces on one address makes it a site, whose sitemap indexes each space inside it, so you submit a single address to Search Console.

2. Prove it

The same three proofs. Serving the file is usually easiest here: it is a site you already control.

3. Forward the path to origin.forumulate.com

No DNS record. One rule in your own reverse proxy, and it must pass the original Host header.

nginx:

location /docs/ {
    proxy_pass https://origin.forumulate.com;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_ssl_server_name on;
}

Caddy:

@forumulate path /docs /docs/*
reverse_proxy @forumulate https://origin.forumulate.com {
    header_up Host {host}
}

Cloudflare: an Origin Rule (or a Worker) that, for requests matching yourcompany.com/docs/*, sets the origin to origin.forumulate.com and preserves the Host header.

Point it at origin.forumulate.com, and at nothing else. Two other plausible targets both fail, each in a way that is hard to diagnose:

  • forumulate.com sits behind a CDN, and a CDN edge answers 403 to a hostname it does not serve. Your request never reaches us.

  • the CNAME target from the whole-host flow serves no certificate for its own name. nginx does not verify upstream TLS, so it appears to work; Caddy and Cloudflare Full (strict) verify, and abort the handshake. That asymmetry is why one customer's path mount worked and another's did not for the same configuration.

Your proxy keeps its own certificate. We never need one for your domain in this shape.

4. Verify, then activate

Same two steps. Here the ordering matters even more: the path is already serving your own application, so verify with {"activate": false}, add the proxy rule, then activate.

Sharing a proxy with us

If your own application runs behind the same Traefik we do, the routers we write for your path win it by design, not by rule length: each carries priority: 1000, which outranks any router whose priority is left to Traefik's default (the length of its rule). You do not need to lower your own routers' priority to make room for us. To take a sub-path back on purpose — a legacy redirect under /docs, say — give your router an explicit priority above 1000.

Plain http:// for the path is answered by a permanent redirect to https:// from our side, so the path is HTTPS-only whatever your application does on port 80.

Nested mounts: under your own app, or under another mount

A mount does not have to sit beside the others. Two arrangements work, and customers run both in production:

  • Under a path your own application owns. Your app serves /, and Forumulate serves /shop — a site, or one space. Your app keeps every path outside /shop; your proxy forwards only /shop/* to us.

  • Under another of your mounts. A site at shop.example.com/shop and a space at shop.example.com/shop/help. The help space answers its own address; the site answers everything else under /shop.

The longest prefix wins. For each request we look at every live mount of the hostname and take the longest basePath the path falls under, on a segment boundary: the path equals the mount, or starts with the mount followed by /. With /shop and /shop/help both live:

Request path

Answered by

/shop, /shop/blog/x

the /shop mount

/shop/help, /shop/help/x

the /shop/help mount

/shop/helpdesk

the /shop mount (helpdesk is not help/…)

/about

not us: no mount covers it

GET /public/domains/resolve?host=…&path=… gives the same answer as the reader does. Use it to check a path before you announce it.

Only live mounts take part, meaning verified and activated. That gives you a clean cut-over for a nested mount: until you activate /shop/help, the /shop mount keeps answering /shop/help/… exactly as it did before.

What this means in practice:

  • No second proof. A hostname your organization has already proved covers every path under it, so a mount added beneath a proved one is created verified. It is not created active. Its setup reports pending until you POST …/activate.

  • No second proxy rule. If your proxy already forwards /shop/* to origin.forumulate.com, it is already forwarding /shop/help/*. Once the nested mount is activated, setup reports verified (served) without your proxy changing at all.

  • Paths outside every mount are never ours. Forward only the mounts. A request that reaches us for a path no mount covers (/about above, if your proxy forwarded the whole host) is redirected (307) to the same path on our own address, not served.

  • One owner per exact address. The same hostname + basePath cannot be registered twice (409 domain.taken). Overlapping paths are allowed; that is what nesting is. POST …/domains/preview lists what the hostname already publishes in otherAddressesOnHost, so you can see the overlap before you create anything.

  • A space nested inside its own site. If the /shop/help space is also a member of the /shop site, it keeps the site's header and footer at its own address. The site's header links to it at /shop/help, which is its address on the same host, not at /shop/<slug>.

What GET …/setup says for a nested mount, in order:

State of the nested mount

mode

served

Proved (or covered by a proved hostname), not activated

pending

false

Our edge answers the path as this mount

verified

true

Our edge answers the path as a PARENT mount (/shop serving /shop/help/…), because this one is not live — not proved yet, or activated moments ago

pending

false

Not proved yet, nothing of ours answering

unverified

false

Activated in the last 15 minutes, edge not seen yet

pending

false

Activated more than 15 minutes ago, still not reaching us

path

false

Our edge names the mount that answered each request in an x-forumulate-mount header (/shop/help, or / for a whole host), next to x-forumulate-edge. setup counts an answer only when it names this mount, so a live parent answering a nested path is reported as the parent answering — never as the nested address being live. curl -sI shows the header too, which is the quickest way to see which mount a path reaches.

path comes with a forward block holding the proxy rule. For a mount nested under one you already forward, path almost always means the outer rule does not match the inner path, for example a location = /shop that matches exactly instead of the prefix location /shop/. The setup of a site's domain (/sites/{siteId}/domains/{domainId}/setup) works the same way.

If you put Cloudflare in front of your domain

Both of these are rules you add once, and both have bitten customers.

Turn Email Obfuscation off for the mounted paths

Cloudflare's Email Obfuscation rewrites email addresses in the HTML and injects a decoder script from /cdn-cgi/scripts/. That script carries no CSP nonce, so the browser refuses to run it, and every address on the page renders as [email protected] with a dead link. Add a Configuration Rule that sets Email Obfuscation to Off for the paths we serve.

npx --yes @forumulate/cli doctor https://yourcompany.com/docs fails its html-untouched check when this is still on.

Cache rule: respect the origin's Cache-Control

We already decide, per request, whether a reader response may be shared. An anonymous GET of a public reader document comes back

Cache-Control: public, s-maxage=60, stale-while-revalidate=60, max-age=0, must-revalidate
Vary: Accept-Encoding

and everything else — a request carrying a session cookie, a cf_share_* private-space cookie, a cflx_vis_* visitor-gate cookie, a Next RSC data request, or a ?share= / ?draft= / ?preview= parameter — comes back

Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate

So the correct Cloudflare Cache Rule is: for the paths we serve, mark them eligible for cache and set Edge TTL and Browser TTL to use the Cache-Control header from the origin.

Do not use "Cache Everything" with an Edge TTL override. An override ignores no-store, which means Cloudflare can store a signed-in author's render — staff bar, edit affordances and all — and serve it to the public, or cache a redirect to sign-in.

Do not raise the TTL either, however tempting 1 hour looks. The window is a guarantee, not a default. Cache-Control is set before the page knows what status it will render, so the same header goes onto a 404. With a longer window: request a URL once before the page exists and your CDN holds the 404 while the new page is invisible; delete a page and your CDN keeps serving the last 200 while the retired page stays publicly readable. Both were reproduced on a customer domain, where the origin answered 200 and the edge served HTTP 404, cf-cache-status: HIT, age: 375.

If you connect your Cloudflare token under Settings → Custom domains, we purge each page's addresses as it publishes or is deleted, and the transition is immediate rather than TTL-bound. One thing worth knowing if you ever script a purge yourself: purge by URL does not evict reader HTML. We measured it on our own zone, twice, with a token that holds Cache Purge — an entry at cf-cache-status: HIT, age: 13 purged by its exact URL answers "success": true and goes on serving at age: 13, while a /_next/static/* asset at the same host purges by URL instantly. The form that works is {"prefixes":["your.domain/the/path"]}, which clears the page (and anything under it) and leaves its siblings alone. That is what we send.

Without a token, or on a CDN we cannot call, the TTL itself is what bounds every transition — which is why it is short.

As belt and braces you can add a cache bypass on cookie presence for forumulate_session, cf_share_* and cflx_vis_*. It should never fire, because those requests already carry no-store.

Restrict who may read the address

A custom domain took a hostname and a base path and nothing else, so a space published on your own domain was published to the whole internet or not at all. Each domain now carries an IP allow-list: the networks that address may be read from.

PUT /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/ip-allow-list
  { "ipAllowList": ["203.0.113.0/24", "198.51.100.7", "2001:db8::/32"] }
# → { "ipAllowList": [ … ] }   the list as it is now stored

Each entry is an IP address or a CIDR block, v4 or v6. Up to 200 of them, 64 characters each. The reply, and ipAllowList on every row of GET …/domains, is the list as stored.

Four things worth knowing before you set one:

  • An empty list means anywhere, which is what every domain has until you say otherwise. {"ipAllowList": []} is how you remove the restriction, not how you lock the door.

  • It is the whole list. The PUT replaces what is stored, the same shape as the redirect table: read it, edit it, send it back. There is no per-entry call, because "delete the one entry that is wrong" would otherwise be two.

  • An entry we cannot parse is refused — 422 validation.failed, with the offending entries in error.details.ipAllowList. An unreadable rule can never match, so storing one would lock the world out silently, and the moment to say so is while you are looking at the form.

  • It is per domain, not per space. The same space can answer openly on one address and only to your network on another, which is how you put an internal handbook on handbook.yourcompany.com while the public docs stay public.

A refused request gets 403 and a one-line text/plain body, never a page and never a sign-in prompt: the reader is on the right address and is being told their network is not on the list. The response carries x-forumulate-gate: ip-allow-list so you can tell this refusal apart from a routing failure, and Cache-Control: private, no-store — a cached 403 on a shared edge would lock out every network behind it.

Which address we compare. The one your edge resolved, in this order: CF-Connecting-IP, then X-Real-IP, then the first entry of X-Forwarded-For — but only when the hop that delivered the request is one we trust: Cloudflare's published ranges, or a proxy an operator has named. Those three headers are written by whoever sends the request, so trusting them unconditionally would make this list advisory: anyone who knew our origin address could send CF-Connecting-IP: and a value you had allowed. When the delivering hop is not trusted, the address we compare is that hop itself — the connection, which a sender cannot choose — and the headers it sent about itself are ignored.

A request with nothing to judge is refused rather than admitted. This is the same definition of "who is asking" the API's rate limiter uses, so the two tiers never disagree.

The check runs in the edge middleware, before a page is looked up or a byte is rendered, and a change evicts the cached domain lookup immediately rather than when the TTL feels like it.

Restricting origin traffic to Cloudflare's ranges

If your domain is fronted by your own Cloudflare and what you want is "nothing reaches my documentation except through my edge", the allow-list is the control — but it needs one setting on the Cloudflare side first, because of the order above. Cloudflare faithfully forwards the reader's address in CF-Connecting-IP, so an allow-list of Cloudflare's own ranges would refuse every genuine reader: the address we compare would be the reader's, not Cloudflare's.

  1. In Cloudflare, Rules → Settings → Managed Transforms, enable Remove visitor IP headers (Cloudflare's name for it, not ours). That strips CF-Connecting-IP, True-Client-IP and X-Forwarded-For from the request Cloudflare sends us, so the address we resolve is the Cloudflare edge that connected.

  2. Put Cloudflare's published ranges on the allow-list. They are served as plain text at https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6:

RULES=$( { curl -s https://www.cloudflare.com/ips-v4; \
           curl -s https://www.cloudflare.com/ips-v6; } \
         | sed '/^$/d' | jq -R . | jq -s . )
curl -sS -A 'my-app/1.0' -X PUT -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  "https://forumulate.com/api/orgs/$ORG/spaces/$SPACE/domains/$DOMAIN/ip-allow-list" \
  -d "{\"ipAllowList\": $RULES}"

A request that reaches us without going through your Cloudflare — anything aimed at our origin with your Host header on it — then arrives from an address that is not in the list and gets the 403.

Two costs, both real, and both the reason this is a separate section rather than the default advice:

  • You give up the reader's address on that domain. With the headers stripped we see Cloudflare, so you cannot also allow-list your office network on the same domain, and anything downstream that works per address — the reader API's per-address rate limit, the addresses in your own analytics — sees your edge instead.

  • Cloudflare's ranges change. They are published, not constant. Because the PUT is the whole list, re-sending the two files on a schedule is the maintenance, and the day a new range appears without you is the day that slice of your readers gets a 403.

If what you actually want is "only my staff may read this", skip all of that and put your own egress addresses on the list: the reader's address is exactly what the default order resolves, and nothing on the Cloudflare side needs changing.

A sign-in domain for your readers

Readers who sign in through Atlas get a session cookie. If that cookie is set on our domain, a browser may refuse to send it from your docs domain. A reader domain is a hostname of yours, such as readers.yourcompany.com, that serves reader sign-in so the cookie is first-party to your parent domain. It is one per organization, separate from the hostnames above, and needs the org.manage_settings capability. It only applies to organizations on Atlas; otherwise the calls answer 409 atlas.not_configured.

PUT /orgs/{orgId}/reader-domain
{ "host": "readers.yourcompany.com" }

The host must be a hostname of at least two labels, 3 to 253 characters (anything else is a 422); it is stored lowercased. The response is { "domain": { ... } } with domainId, host, status, live, cnameTarget and cookieDomain. Publish a CNAME from host to cnameTarget, then ask us to re-check it:

POST /orgs/{orgId}/reader-domain/verify

verify is a 404 until a PUT has created the domain. When DNS points where it should, live turns true. GET /orgs/{orgId}/reader-domain returns the current domain or { "domain": null }, and DELETE on the same address unbinds it ({ "removed": true }).

Troubleshooting

403 from the edge, or an HTML error page instead of your docs. Your proxy is pointed at forumulate.com rather than origin.forumulate.com, or it is not passing the original Host. Check with the Host you expect us to see:

curl -sI https://origin.forumulate.com/docs/ -H 'Host: yourcompany.com'

A 200 or a redirect means the mount is right and the problem is in your proxy. A 403 means the Host we received is not one we serve — the domain is not activated, or the hostname has a typo.

The address answers your own application, not us. The domain is verified but not activated. GET …/setup reports mode: "pending". Call POST …/activate.

TLS handshake failures from Caddy or Cloudflare Full (strict). You are proxying to a hostname that does not serve a certificate for its own name. Use origin.forumulate.com.

Verified, DNS pointed, still a 404 on every page. The space is private, or its pages are unpublished. A private space answers an anonymous request with nothing, which looks identical to a routing failure. Make the space public, or check that a page is published, before suspecting DNS.

It worked and then stopped. We re-check a verified name's DNS over time and record when it last pointed somewhere other than us. A domain is proved once and then trusted forever; DNS is not forever. The panel surfaces the drift.

Check it from the outside

npx --yes @forumulate/cli doctor https://docs.yourcompany.com

That checks, from outside your network, that an anonymous reader gets the page rather than a sign-in, that the canonical link names your host and not ours, that robots.txt allows the path and names a sitemap, that a crawler gets the same answer a reader does, that the document is cacheable, and that nothing in the path is rewriting your markup.

Then fetch https://docs.yourcompany.com/sitemap.xml: every URL in it is on your domain. View the source of any page — its <link rel="canonical"> names your address, not ours. That is the whole point: a canonical on our domain would ask Google to rank us for your content.

Two public lookups answer the same questions our own reader asks, with no token:

  • GET /public/domains/resolve?host=docs.yourcompany.com&path=/docs says what an address serves. The longest registered path prefix under path answers: space (with orgId, spaceId, type and basePath) for a single space, site (with its spaces) for a domain that carries a whole site, or library for a content library's hosted output. All three are null when the address serves nothing of ours.

  • GET /public/domains/primary?org=<org>&space=<space> returns the canonical address for a space's copy on our domain: your verified custom domain or the site it is part of, as { origin, basePath, mode }. null means our address is its own canonical. org and space take a slug or an id; give library instead of space for a content library's hosted output.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.

Domain

Operation

Request

What it does

Shortcuts

Domain_list

GET /orgs/{orgId}/spaces/{spaceId}/domains

List domains in a space

forumulate domains list<br>client.domains.list()

Domain_add

POST /orgs/{orgId}/spaces/{spaceId}/domains

Create a domain in a space

forumulate domains add<br>client.domains.add()

Domain_allocateSubdomain

POST /orgs/{orgId}/spaces/{spaceId}/domains/subdomain

Create the subdomain in a space

forumulate domains subdomain<br>client.domains.allocateSubdomain()

Domain_setAcmeChallenge

POST /orgs/{orgId}/spaces/{spaceId}/domains/acme-challenge

Create the acme challenge in a space

forumulate domains acme-challenge<br>client.domains.setAcmeChallenge()

Domain_verify

POST /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/verify

Verify the domain (spaces)

forumulate domains verify<br>client.domains.verify()

Domain_activate

POST /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/activate

Activate the domain (spaces)

forumulate domains activate<br>client.domains.activate()

Domain_preview

POST /orgs/{orgId}/spaces/{spaceId}/domains/preview

Preview the domain

forumulate domains preview<br>client.domains.preview()

Domain_remove

DELETE /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}

Delete the domain (spaces)

forumulate domains remove<br>client.domains.remove()

Domain_setup

GET /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/setup

Get the setup in a space

forumulate domains setup<br>client.domains.setup()

Domain_setWww

PUT /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/www

Replace the www in a space

forumulate domains www<br>client.domains.setWww()

Domain_setIpAllowList

PUT /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/ip-allow-list

Replace the IP allow list in a space

forumulate domains allow-ips<br>client.domains.setIpAllowList()

Domain_setRedirect

PUT /orgs/{orgId}/spaces/{spaceId}/domains/{domainId}/redirect

Replace the redirect in a space

forumulate domains redirect<br>client.domains.setRedirect()

Reader Domain

Operation

Request

What it does

Shortcuts

ReaderDomain_get

GET /orgs/{orgId}/reader-domain

Get the reader domain in an organization

client.readers.domain.get()

ReaderDomain_configure

PUT /orgs/{orgId}/reader-domain

Replace the reader domain in an organization

client.readers.domain.configure()

ReaderDomain_remove

DELETE /orgs/{orgId}/reader-domain

Delete the reader domain in an organization

client.readers.domain.remove()

ReaderDomain_verify

POST /orgs/{orgId}/reader-domain/verify

Verify the reader domain

client.readers.domain.verify()

The Forumulate teamUpdated