# SaaS Custom Domains API documentation

> The complete API reference and guides from https://docs.saascustomdomains.com in one file. The OpenAPI spec is at https://docs.saascustomdomains.com/openapi.json.

<!-- Source: https://docs.saascustomdomains.com -->

# API Documentation

Use the SaaS Custom Domains API to programmatically access upstreams, custom domains, and other resources to seamlessly integrate custom domains into your product.

[Quickstart](https://docs.saascustomdomains.com/quickstart)

## Getting started

To get started, create a new API token in your [API settings](https://app.saascustomdomains.com/api_tokens), then read about how to make requests for the resources you need to access using our HTTP APIs.

[Get your API key](https://app.saascustomdomains.com/api_tokens)

## [Guides](https://docs.saascustomdomains.com/#guides)

### Authentication

Learn how to authenticate your API requests.

[Read more](https://docs.saascustomdomains.com/authentication)

### Pagination

Understand how to work with paginated responses.

[Read more](https://docs.saascustomdomains.com/pagination)

### Handling Forwarded Requests

Read about how to handle forwarded requests from custom domains.

[Read more](https://docs.saascustomdomains.com/handling-forwarded-requests)

### Custom Domain Types

Understand the different types of custom domains and how to use them.

[Read more](https://docs.saascustomdomains.com/custom-domains-guide)

### Webhooks

Learn how to use webhooks to receive notifications about events in the API.

[Read more](https://docs.saascustomdomains.com/webhooks)

### Rate Limits

Learn about the rate limits that apply to the API and how to handle them.

[Read more](https://docs.saascustomdomains.com/rate-limits)

### Errors

Read about the different types of errors returned by the API.

[Read more](https://docs.saascustomdomains.com/errors)

## [Resources](https://docs.saascustomdomains.com/#resources)

### [Upstreams](https://docs.saascustomdomains.com/upstreams)

Learn about the Upstream model and how to create, retrieve, update, delete, and list upstreams.

### [Custom Domains](https://docs.saascustomdomains.com/custom-domains)

Learn about the Custom Domain model and how to create, retrieve, update, delete, and list custom domains.

### [Custom Certificates](https://docs.saascustomdomains.com/custom-certificates)

Learn about the Custom Certificate model and how to create, retrieve, update, delete, and list custom domains.

### [Accounts](https://docs.saascustomdomains.com/accounts)

Learn about the account model and how to access accounts programmatically..

---

<!-- Source: https://docs.saascustomdomains.com/quickstart -->

# Quickstart

This guide will get you all set up and ready to use the SaaS Custom Domains API. We'll cover how to get started and make your first API request. We'll also look at where to go next to find all the information you need to take full advantage of our powerful REST API.

Before you can make requests to the SaaS Custom Domains API, you will need to grab your API key from your dashboard. You find it under [Settings » API](https://app.saascustomdomains.com/api_tokens).

## [Base URL](https://docs.saascustomdomains.com/quickstart#base-url)

The base URL for all the API requests is `https://app.saascustomdomains.com`. All the API endpoints, e.g. `/api/v1/me`, are relative to the base URL. In the example of `/api/v1/me`, the full URL where you send your HTTP request would be `https://app.saascustomdomains.com/api/v1/me`.

## [Making your first API request](https://docs.saascustomdomains.com/quickstart#making-your-first-api-request)

Once you have your API token, you are ready to make your first call to the SaaS Custom Domains API. Below, you can see how to send a GET request to the /me endpoint data about the user to whom the API token belongs. This is just a simple example, but you can use the same approach to make any API request.

GET

/api/v1/me

```bash
curl https://app.saascustomdomains.com/api/v1/me \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {token}"

```

### Response

```json
{
    "id": 1,
    "name": "Rocky Balboa",
    "avatar_url": "https://secure.gravatar.com/avatar/xxxxxxx.png?default=mp&rating=pg&size=48"
}

```

[Read the docs for the Upstreams endpoint](https://docs.saascustomdomains.com/upstreams)

## [What's next?](https://docs.saascustomdomains.com/quickstart#what-s-next)

Great, you're now set up with an API token and have made your first request to the API. Here are a few links that might be handy as you venture further into SaaS Custom Domains API:

* [Check out the Upstreams endpoint](https://docs.saascustomdomains.com/upstreams)
* [Learn about the different error messages in SaaS Custom Domains API](https://docs.saascustomdomains.com/errors)

---

<!-- Source: https://docs.saascustomdomains.com/mcp -->

# MCP server

The SaaS Custom Domains MCP server lets AI assistants and coding agents manage your custom domains. Ask your assistant to add a customer's domain, tell you which DNS records they need, check why a domain isn't live yet, or show last month's traffic.

The server is remote, so there's nothing to install. Point your MCP client at this URL:

```text
https://mcp.saascustomdomains.com/mcp

```

It uses the Streamable HTTP transport. There are two ways to connect:

* **Sign in** with your SaaS Custom Domains account (OAuth). Every client on this page supports this, and there's no token to copy.
* **Send an [API token](https://app.saascustomdomains.com/api_tokens)** as `Authorization: Bearer <token>`. This works in every client that lets you set a request header.

## [What it can do](https://docs.saascustomdomains.com/mcp#what-it-can-do)

Tools take hostnames, like `app.customer.com`, rather than UUIDs. Tools that work in one account take an optional `account` (its name or UUID). You only need to pass it if you have more than one account.

| Tool                        | What it does                                                                                                                          |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| list\_accounts              | Lists the accounts your token can access.                                                                                             |
| list\_upstreams             | Lists an account's upstreams.                                                                                                         |
| list\_custom\_domains       | Lists custom domains across your accounts, filtered by account, upstream or DNS status.                                               |
| get\_custom\_domain         | Shows a domain's DNS, certificate and deployment status, and the DNS records it needs.                                                |
| list\_redirects             | Lists an account's redirects.                                                                                                         |
| list\_rules                 | Lists the rules of a custom domain or an upstream, in the order they run.                                                             |
| list\_custom\_certificates  | Lists the TLS certificates an account uploaded in the dashboard.                                                                      |
| get\_traffic\_stats         | Shows requests, data transfer, response time and response codes for a domain.                                                         |
| get\_web\_analytics         | Shows visitors, pageviews, visits, top pages and top sources for a domain.                                                            |
| get\_analytics\_over\_time  | Shows one metric per day, week or month, such as visitors, requests or response time.                                                 |
| get\_analytics\_breakdown   | Breaks traffic down by pages, sources, locations, devices, UTM parameters, bots, datacenters or response times.                       |
| create\_upstream            | Creates an upstream.                                                                                                                  |
| add\_custom\_domain         | Adds a custom domain to an upstream and returns the DNS records to add. Also takes a www redirect, a path prefix and Bubble settings. |
| verify\_dns                 | Checks a domain's DNS records right away.                                                                                             |
| purge\_cache                | Purges a domain's HTTP cache.                                                                                                         |
| create\_redirect            | Creates a redirect.                                                                                                                   |
| update\_redirect            | Changes a redirect.                                                                                                                   |
| create\_rule                | Creates a rule on a custom domain or an upstream.                                                                                     |
| update\_rule                | Changes a rule.                                                                                                                       |
| reorder\_rules              | Changes the order rules run in, for example to move a new rule first.                                                                 |
| delete\_custom\_domain      | Deletes a custom domain.                                                                                                              |
| delete\_redirect            | Deletes a redirect.                                                                                                                   |
| delete\_rule                | Deletes a rule.                                                                                                                       |
| delete\_custom\_certificate | Deletes an uploaded certificate.                                                                                                      |
| delete\_upstream            | Deletes an upstream that has no custom domains left.                                                                                  |

The list and get tools only read data. The delete tools are marked as destructive, so most clients ask you to confirm before running them. `delete_upstream` refuses an upstream that still has custom domains, because deleting it would delete them too.

The four analytics tools need the Domain Analytics add-on. They cover up to 90 days and default to the last 30.

## [Set up your client](https://docs.saascustomdomains.com/mcp#set-up-your-client)

Claude Code, Codex and VS Code can also install the [SaaS Custom Domains plugin](https://github.com/saas-custom-domains/skills), which connects this server and adds the [agent skill](https://docs.saascustomdomains.com/.well-known/agent-skills/saas-custom-domains/SKILL.md) in one step. For step-by-step setup in each agent, see [Agent setup](https://saascustomdomains.com/agent-setup).

### claude.ai

1. Go to **Customize → Connectors** and click **Add custom connector**. On Team and Enterprise plans, an owner adds it under **Organization settings → Connectors**, and members then click **Connect**.
2. Enter `https://mcp.saascustomdomains.com/mcp` and connect.
3. Approve the connection, sign in to SaaS Custom Domains, and click **Continue**.

### ChatGPT

Add `https://mcp.saascustomdomains.com/mcp` as a custom MCP server with OAuth authentication, then approve the connection, sign in to SaaS Custom Domains, and click **Continue**. Custom MCP servers depend on your ChatGPT plan; see OpenAI's [guide to connecting a custom MCP server](https://developers.openai.com/api/docs/guides/custom-mcp-server).

### Claude Code

To sign in, add the server without a header:

### Terminal

```bash
claude mcp add --transport http saas-custom-domains https://mcp.saascustomdomains.com/mcp --scope user

```

Then run `/mcp` in Claude Code, pick **saas-custom-domains** and choose **Authenticate**. Your browser opens to approve the connection and sign in.

To use an API token instead, add the header:

### Terminal

```bash
claude mcp add --transport http saas-custom-domains https://mcp.saascustomdomains.com/mcp \
  --header "Authorization: Bearer YOUR_API_TOKEN" --scope user

```

`--scope user` makes the server available in every project. Leave it out to add it to the current project only.

Cursor, VS Code, Windsurf, Cline and Codex can sign in too: add the server without the `Authorization` header, and the client opens the sign-in page when it first connects. The configs below send an API token instead, which also works where nobody can sign in, such as CI. Create one in [Settings » API](https://app.saascustomdomains.com/api_tokens) first. The assistant can do anything the token can, so treat the token like a password.

### Cursor

Add the server to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in a project. This reads the token from the `SCD_API_TOKEN` environment variable:

### \~/.cursor/mcp.json

```json
{
  "mcpServers": {
    "saas-custom-domains": {
      "url": "https://mcp.saascustomdomains.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SCD_API_TOKEN}"
      }
    }
  }
}

```

### VS Code

Add this to `.vscode/mcp.json`. VS Code asks for the token the first time the server starts and stores it securely:

### .vscode/mcp.json

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "scd-api-token",
      "description": "SaaS Custom Domains API token",
      "password": true
    }
  ],
  "servers": {
    "saasCustomDomains": {
      "type": "http",
      "url": "https://mcp.saascustomdomains.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:scd-api-token}"
      }
    }
  }
}

```

### Windsurf (Devin Desktop)

Add the server to `~/.config/devin/mcp_config.json`. Older Windsurf versions use `~/.codeium/windsurf/mcp_config.json`:

### mcp\_config.json

```json
{
  "mcpServers": {
    "saas-custom-domains": {
      "url": "https://mcp.saascustomdomains.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SCD_API_TOKEN}"
      }
    }
  }
}

```

### Cline

Open **MCP Servers → Configure → Configure MCP Servers** and add the server to `cline_mcp_settings.json`. Set `type` to `streamableHttp`, otherwise Cline uses the older SSE transport:

### cline\_mcp\_settings.json

```json
{
  "mcpServers": {
    "saas-custom-domains": {
      "type": "streamableHttp",
      "url": "https://mcp.saascustomdomains.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_TOKEN"
      },
      "disabled": false
    }
  }
}

```

### Codex CLI

To sign in, add the server without a token, then log in. Your browser opens to approve the connection and sign in:

### Terminal

```bash
codex mcp add saas-custom-domains --url https://mcp.saascustomdomains.com/mcp
codex mcp login saas-custom-domains

```

To use an API token instead, this reads it from the `SCD_API_TOKEN` environment variable:

### Terminal

```bash
codex mcp add saas-custom-domains --url https://mcp.saascustomdomains.com/mcp \
  --bearer-token-env-var SCD_API_TOKEN

```

Or add it to `~/.codex/config.toml`:

### \~/.codex/config.toml

```toml
[mcp_servers.saas-custom-domains]
url = "https://mcp.saascustomdomains.com/mcp"
bearer_token_env_var = "SCD_API_TOKEN"

```

### Other clients

Any client that supports remote MCP servers over Streamable HTTP works. Use the URL above, and either sign in when the client asks (OAuth, with Client ID Metadata Documents or dynamic client registration) or send `Authorization: Bearer <token>`.

## [What signing in does](https://docs.saascustomdomains.com/mcp#what-signing-in-does)

When a client connects by signing in, you approve it on our consent page, sign in to SaaS Custom Domains (with two-factor authentication if you use it), and click **Continue**. The client can then do anything you can, in all of your accounts.

We create an API token named after the client, for example **MCP: Claude**. It's listed under [Settings » API](https://app.saascustomdomains.com/api_tokens). Delete it to disconnect the client; the client then asks you to sign in again. A sign-in lasts 30 days.

## [Limits](https://docs.saascustomdomains.com/mcp#limits)

The MCP server calls the [SaaS Custom Domains API](https://docs.saascustomdomains.com/quickstart) with your token, so the [API rate limits](https://docs.saascustomdomains.com/rate-limits) apply: 360 requests per minute per token, and 5 per minute for DNS checks. Each tool call makes one to a few API requests. A token you send in the header isn't stored. A token created when you sign in is stored encrypted and only used for your requests.

---

<!-- Source: https://docs.saascustomdomains.com/cli -->

# CLI

The SaaS Custom Domains CLI lets you add your customers' domains, get the DNS records they need, check why a domain isn't live yet, and manage upstreams, redirects, rules and analytics from your terminal, scripts and coding agents. It's open source: [github.com/saas-custom-domains/cli](https://github.com/saas-custom-domains/cli).

## [Install](https://docs.saascustomdomains.com/cli#install)

The CLI needs Node.js 20 or newer. Run any command without installing it:

### Terminal

```bash
npx @saas-custom-domains/cli domains list

```

Or install it, which adds the `scd` and `saascustomdomains` commands:

### Terminal

```bash
npm install -g @saas-custom-domains/cli

```

## [Sign in](https://docs.saascustomdomains.com/cli#sign-in)

### Terminal

```bash
scd auth login

```

Your browser opens. Sign in to SaaS Custom Domains (with two-factor authentication if you use it) and click **Continue**. The CLI saves an API token named "CLI: <your computer's name>" in `~/.config/saascustomdomains/config.json`, readable only by you. The token is listed under [Settings » API](https://app.saascustomdomains.com/api_tokens); delete it there to sign the CLI out everywhere.

To use an API token instead, which is the usual choice for CI and agents, set `SCD_API_TOKEN`, or save a token with `scd auth login --with-token` (it reads the token from stdin).

`scd auth status` shows who you're signed in as. `scd auth logout` removes the saved token.

## [Quick start](https://docs.saascustomdomains.com/cli#quick-start)

Commands take hostnames rather than UUIDs:

### Terminal

```bash
scd upstreams create app.yourapp.com      # the app your custom domains forward to (once)
scd domains add app.customer.com          # add a customer's domain and get its DNS records
scd domains get app.customer.com          # DNS and certificate status, and the records it needs
scd domains verify app.customer.com       # check the DNS records now
scd domains list --status dns_records_not_found --all   # who still hasn't set up DNS

```

If you have more than one account, pass `--account <name or UUID>` or set `SCD_ACCOUNT`.

## [Commands](https://docs.saascustomdomains.com/cli#commands)

| Command                                         | What it does                                                                                                                                          |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| scd auth login, logout, status                  | Sign in with the browser or an API token, sign out, show the current user                                                                             |
| scd accounts list                               | List your accounts                                                                                                                                    |
| scd upstreams list, get, create, delete         | Manage [upstreams](https://docs.saascustomdomains.com/upstreams). get shows the auth token your app should check                                                                        |
| scd domains list, get, add, delete              | Manage [custom domains](https://docs.saascustomdomains.com/custom-domains). add takes \--upstream, \--redirect-to-www, \--challenge-type, \--instructions-recipient and \--prepend-path |
| scd domains verify, dns-records, purge-cache    | Check DNS now, show the DNS records a domain needs, purge its HTTP cache                                                                              |
| scd redirects list, get, create, update, delete | Manage [redirects](https://docs.saascustomdomains.com/redirects)                                                                                                                        |
| scd rules list, create, delete                  | Manage [rules](https://docs.saascustomdomains.com/rules) on a domain (\--domain) or an upstream (\--upstream)                                                                           |
| scd certificates list, delete                   | Manage [certificates](https://docs.saascustomdomains.com/custom-certificates) uploaded in the dashboard                                                                                 |
| scd analytics <host>                            | Requests, data transfer and response codes; \--web for visitors, pageviews and top pages. Needs the Domain Analytics add-on                           |
| scd api <method> <path>                         | Call any API endpoint, with the path relative to /api/v1, and print the JSON response                                                                 |
| scd skills install                              | Install the [agent skill](https://github.com/saas-custom-domains/skills) for your coding agent                                                        |
| scd commands --json                             | Every command, argument and option, for agents                                                                                                        |

Run `scd <command> --help` for all options.

`scd upstreams delete` refuses an upstream that still has custom domains, because deleting it would delete them too.

## [Scripts, CI and agents](https://docs.saascustomdomains.com/cli#scripts-ci-and-agents)

* Add `--json` to any command for JSON on stdout. Messages go to stderr. With `--json`, errors are printed to stderr as `{"error": {"status": 404, "message": "..."}}`.
* Deleting asks for confirmation in a terminal. Without one, or with `--no-input`, it fails unless you pass `--yes`.
* The [API rate limits](https://docs.saascustomdomains.com/rate-limits) apply. On a `429`, the CLI says when to retry.

| Exit code | Meaning                                         |
| --------- | ----------------------------------------------- |
| 0         | Success                                         |
| 1         | Error                                           |
| 2         | Bad usage, or input needed (for example \--yes) |
| 4         | Not signed in, invalid token, or no access      |
| 5         | Not found                                       |
| 6         | Rate limited                                    |

For example, in CI:

### Terminal

```bash
SCD_API_TOKEN=$SCD_API_TOKEN npx @saas-custom-domains/cli domains add "$CUSTOMER_DOMAIN" --upstream app.yourapp.com --json

```

## [Test locally with scd-mock](https://docs.saascustomdomains.com/cli#test-locally-with-scd-mock)

[scd-mock](https://github.com/saas-custom-domains/scd-mock) runs the API and proxy locally, so you can try the CLI without an account:

### Terminal

```bash
export SCD_API_URL=http://localhost:4010/api/v1 SCD_API_TOKEN=scd_test_token SCD_ACCOUNT=acc_local
scd domains add acme.localhost --upstream localhost

```

## [CLI, MCP server or API?](https://docs.saascustomdomains.com/cli#cli-mcp-server-or-api)

* **CLI**: your terminal, shell scripts, CI, and coding agents that run commands.
* **[MCP server](https://docs.saascustomdomains.com/mcp)**: chat assistants and agents that connect to tools, such as claude.ai, ChatGPT and Cursor.
* **[REST API](https://docs.saascustomdomains.com/quickstart)**: your app's own code, for example adding a domain when a customer saves it in their settings.

All three use the same API token and the same permissions.

---

<!-- Source: https://docs.saascustomdomains.com/authentication -->

# Authentication

You'll need to authenticate your requests to access any of the endpoints in the SaaS Custom Domains API. In this guide, we'll look at how authentication works.

## [Bearer authentication](https://docs.saascustomdomains.com/authentication#bearer-authentication)

When establishing a connection, you will need your API token — you will find it in the [Settings » API](https://app.saascustomdomains.com/api_tokens). Here's how to add the token to the request header using cURL:

### Example request with bearer token

```bash
curl https://app.saascustomdomains.com/api/v1/me \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {token}"

```

Always keep your token safe and revoke it if you suspect it has been compromised. You can always create a new API token. You can also create multiple API tokens.

---

<!-- Source: https://docs.saascustomdomains.com/pagination -->

# Pagination

In this guide, we will look at how to work with paginated responses when querying the SaaS Custom Domains API. By default, all responses limit results to 20 per page.

When an API response returns a list of objects, no matter the amount, pagination is supported. In paginated responses, objects are nested in a `data` attribute and have a `pagination` attribute that holds the information about what page has been returned and the number of items in the collection. You can use the `page` query parameter to browse pages — pages start from 1.

## [Example](https://docs.saascustomdomains.com/pagination#example)

In this example, we request the second page of our upstreams. As a result, we get a list of 20 upstreams.

* `page` (integer):  
The page number to return. Defaults to 1.

### Manual pagination using cURL

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams \
  -H "Authorization: Bearer {token}" \
  -d page=2

```

### Paginated response

```json
{
  "data": [
    {
      "uuid": "upstream_21",
      // ...
    },
    {
      "uuid": "upstream_22"
      // ...
    },
    // ...
    {
      "uuid": "upstream_40"
      // ...
    },
  ],
  "pagination": {
    "page": 2,
    "count": 237
  }
}

```

---

<!-- Source: https://docs.saascustomdomains.com/custom-domains-guide -->

# Custom domain types

## [Domain types](https://docs.saascustomdomains.com/custom-domains-guide#domain-types)

To use SaaS Custom Domains effectively, you need to know about 3 different types of custom domains supported at SaaS Custom Domains: **subdomains**, **apex domains**, and **wildcard domains**.

## [Subdomains](https://docs.saascustomdomains.com/custom-domains-guide#subdomains)

A subdomain is a part of a larger domain. It's a way to organize and navigate the hierarchy of a website, and it typically reflects specific content or functions of the site.

For example, in the domain **blog.example.com**, **blog** is the subdomain of the main domain **example.com**. Subdomains can be used to separate different areas of a website, such as a blog, shop, or support section. They are helpful in organizing content and can be managed separately, allowing for different hosting, security settings, or even branding.

### How to add a custom subdomain?

Suppose you want to create a custom subdomain like **custom-subdomain.example.com**.

1. **Select the Upstream:** Choose the upstream for which you wish to create a custom domain.
2. **Create the Custom Domain:** Navigate to the dashboard and create the custom domain.
3. **Configure a CNAME Record:** Create a CNAME record at **custom-subdomain.example.com** that points to **in.saascustomdomains.com**.
4. **Verify and Access:** That's it! You're all set. Visit **custom-subdomain.example.com**, and you should see your app.

## [Apex Domains](https://docs.saascustomdomains.com/custom-domains-guide#apex-domains)

An apex domain, also known as a root domain or naked domain, is the primary address of a site without any subdomains or prefixes. It's the core domain name that users type into their browser to access a website.

For example, **example.com** is an apex domain. It's the fundamental, top-level domain that doesn't include **www** or any other subdomains.

### How to add a custom apex domain?

Suppose you want to create a custom apex domain like **example.com**.

1. **Select the Upstream:** Choose the upstream for which you wish to create a custom domain.
2. **Create the Custom Domain:** Navigate to the dashboard and create the custom domain.
3. **Configure A Records:** Create two A records at **example.com** that point to **99.83.186.151** and **75.2.96.173**. Both are required, because we use two IP addresses for high availability. A domain with one wrong IP can still load, but it won't verify.
4. **Verify and Access:** That's it! You're all set. Visit **example.com**, and you should see your app.

Don't add AAAA (IPv6) records for the domain, and remove any old A or AAAA records that point elsewhere. We don't offer IPv6 addresses, and stray records stop certificates from being issued.

If you create the domain with `redirect_to_www` turned on, also create a CNAME record at **[www.example.com](http://www.example.com)** that points to **in.saascustomdomains.com**.

## [Wildcard Domains](https://docs.saascustomdomains.com/custom-domains-guide#wildcard-domains)

Wildcard domains are used to match requests for non-existent subdomains. They are specified by using an asterisk **\*** as part of the domain name, usually at the subdomain level.

A wildcard domain might look like **\*.example.com**, and it will match requests for any subdomain that hasn't been specifically defined within **example.com**. This is useful in scenarios where you want to dynamically generate subdomains or have a catch-all routing for various subdomains.

For example, you might use wildcard domains to create personalized URLs for users, like **john.example.com** or **sarah.example.com**, without having to create a custom domain and DNS record for each user. You'd set up the wildcard domain once, as shown below, and then handle the routing within your application.

Another use-case for wildcard domains is that they can be used to create a catch-all routing for subdomains. For example, you might want to redirect all requests for non-existent subdomains to a specific page or URL. You can do this by creating a wildcard domain and then handling the routing within your application.

A wildcard matches one level of subdomains: **\*.example.com** covers **john.example.com** but not **a.b.example.com**. Wildcards on public suffixes, such as **\*.co.uk**, are rejected.

### How to add a custom wildcard domain?

Wildcard domains must use the [DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) and get a single wildcard certificate for **\*.example.com** that covers every subdomain. The API rejects a wildcard domain with the `http01` challenge type ("Wildcard domains must use DNS-01 challenge"), and the dashboard disables that option for wildcard hosts.

Suppose you want to create a custom wildcard domain like **\*.example.com**.

1. Choose the upstream for which you wish to create a custom wildcard domain.
2. Create the custom domain **\*.example.com** with the DNS-01 challenge type (`"challenge_type": "dns01"` in the API).
3. Create a CNAME record at **\*.example.com** that points to **in.saascustomdomains.com**. If you can't add a wildcard record, create a CNAME record for each subdomain instead, e.g. **john.example.com** and **sarah.example.com**, pointing to **in.saascustomdomains.com**.
4. Create a CNAME record at **\_acme-challenge.example.com** that points to **\_acme-challenge.example.com.challenges.saascustomdomains.com**. This record lets the certificate authority verify that you own the domain. Keep it in place and set to DNS only (not proxied), because it's needed for every certificate renewal.
5. Visit **john.example.com** or **sarah.example.com**, and you should see your app, served with the certificate for **\*.example.com**.

Every subdomain of a wildcard domain that received requests in the last 30 days counts as a domain on your bill, including requests from bots to random subdomains. If you only need a handful of subdomains, create them as separate custom domains.

## [Use your own CNAME target](https://docs.saascustomdomains.com/custom-domains-guide#use-your-own-cname-target)

Your customers can point their CNAME records at a hostname on your domain, such as **in.yourapp.com**, that is itself a CNAME to **in.saascustomdomains.com**. Add it under Account settings » Custom CNAMEs so the DNS instruction emails show it instead of **in.saascustomdomains.com**. This feature is enabled on request; contact support to turn it on.

## [Troubleshooting](https://docs.saascustomdomains.com/custom-domains-guide#troubleshooting)

If a domain doesn't verify or its certificate isn't issued, see the [help center](https://help.saascustomdomains.com) for common causes and fixes.

---

<!-- Source: https://docs.saascustomdomains.com/handling-forwarded-requests -->

# Handling forwarded requests

In this guide, we will look at how to handle forwarded requests coming from your users on their custom domains.

## [Headers on forwarded requests](https://docs.saascustomdomains.com/handling-forwarded-requests#headers-on-forwarded-requests)

Every request we forward to your upstream carries these headers. We set them ourselves and overwrite any value the visitor sent.

| Header                    | Value                                                             |
| ------------------------- | ----------------------------------------------------------------- |
| X-Served-For              | The custom domain the visitor requested, e.g. app.mydomain.com    |
| X-SaaS-Domains-IP         | The visitor's IP address                                          |
| X-SaaS-Domains-Auth-Token | Your upstream's auth token (see below)                            |
| Host                      | Your upstream's host, e.g. app.example.com, not the custom domain |
| X-Forwarded-Host          | Your upstream's host, not the custom domain                       |
| X-Forwarded-Proto         | https                                                             |
| X-Forwarded-For           | The visitor's IP address                                          |

If `geocoding_enabled` is on for the upstream, we also add the [geocoding headers](https://docs.saascustomdomains.com/upstreams#geocoding-headers). Because `Host` is the same for every custom domain, use `X-Served-For` to tell which domain a request was for.

## [X-Served-For and X-SaaS-Domains-IP headers](https://docs.saascustomdomains.com/handling-forwarded-requests#x-served-for-and-x-saa-s-domains-ip-headers)

When a request is forwarded from a custom domain to your upstream, the `X-Served-For` and `X-SaaS-Domains-IP` headers will be added to the request.

`X-Served-For` contains information about the custom domain for which the request was forwarded.

`X-SaaS-Domains-IP` contains information about the IP address of the client sending the request.

## [X-SaaS-Domains-Auth-Token header](https://docs.saascustomdomains.com/handling-forwarded-requests#x-saa-s-domains-auth-token-header)

Every forwarded request also carries the `X-SaaS-Domains-Auth-Token` header, set to your upstream's [auth token](https://docs.saascustomdomains.com/upstreams). Use it to check that a request really came through SaaS Custom Domains.

Your app decides which customer's data to show based on `X-Served-For`. Anyone who can reach your upstream directly could send that header themselves and see another customer's pages. Reject any request that has `X-Served-For` but not the right token. Requests to your own domain, without `X-Served-For`, are unaffected.

New upstreams get a random 32-character token. Upstreams created before October 2026 keep their original 16-character token; it isn't changed automatically, because that would break apps already checking it. You can set your own token (1 to 64 letters, digits, dashes or underscores) from the dashboard or with the `auth_token` field in the API. If you change it, update your app at the same time, or requests will be rejected in between.

Keep the token in an environment variable, check it before you look up the customer, and compare it in constant time. Also reject the request when the expected token is empty: if the variable is set to an empty string in some environment, an empty token compares equal to a missing header, and every request would pass.

### Verify the auth token

```ruby
class ApplicationController < ActionController::Base
  # Runs before any customer lookup
  prepend_before_action :verify_proxy_token

  private

  def verify_proxy_token
    return unless request.headers["X-Served-For"]

    token = request.headers["X-SaaS-Domains-Auth-Token"].to_s
    expected = ENV["PROXY_AUTH_TOKEN"].to_s
    valid = expected.present? &&
      ActiveSupport::SecurityUtils.secure_compare(token, expected)
    head :forbidden unless valid
  end
end

```

## [Requests come from changing IP addresses](https://docs.saascustomdomains.com/handling-forwarded-requests#requests-come-from-changing-ip-addresses)

Our proxies run on AWS and don't use a fixed list of outgoing IP addresses, so don't allowlist IPs to protect your upstream. Check the `X-SaaS-Domains-Auth-Token` header instead.

If a WAF or Cloudflare sits in front of your upstream, add a rule that lets requests with the correct `X-SaaS-Domains-Auth-Token` through. Many custom domains share the same proxy IP addresses, so their combined traffic can look like bot traffic and get blocked or challenged.

## [CDNs and caches in front of your upstream](https://docs.saascustomdomains.com/handling-forwarded-requests#cdns-and-caches-in-front-of-your-upstream)

If a CDN or cache sits in front of your upstream, include `X-Served-For` in its cache key. Every forwarded request has the same `Host`, so without it one customer's cached page, or a cached redirect, can be served on another customer's domain.

## [Example](https://docs.saascustomdomains.com/handling-forwarded-requests#example)

You have an upstream that is listening on `https://app.example.com`.

You have a custom domain `https://app.mydomain.com` that is forwarding requests to `https://app.example.com`.

A client with IP `37.228.203.125` makes a request to `https://app.mydomain.com`.

The `X-Served-For` header will be added to the forwarded request with the value `app.mydomain.com`.

The `X-SaaS-Domains-IP` header will be added to the forwarded request with the value `37.228.203.125`.

The `X-SaaS-Domains-Auth-Token` header will be set to your upstream's auth token, e.g. `abcdef123456`.

The `Host` header will be set to `app.example.com`.

---

<!-- Source: https://docs.saascustomdomains.com/webhooks -->

# Webhooks

Webhooks are a way to receive real-time updates from SaaS Custom Domains. When an event happens in SaaS Custom Domains, we'll send a POST request to the URL you specify with additional data about the event. You can use webhooks to trigger anything from a simple email notification to a complex workflow.

## [Getting Started](https://docs.saascustomdomains.com/webhooks#getting-started)

To start using webhooks, navigate to the [webhooks section](https://app.saascustomdomains.com/webhooks) in the application. Here, you can create a new webhook. You'll have to specify the endpoint URL to receive webhook notifications and select the events you wish to be notified about.

When you create a webhook, we send a test event, `{"event": "test"}`, to your endpoint. The endpoint must respond with a 2xx status code, or the webhook isn't saved.

## [Security](https://docs.saascustomdomains.com/webhooks#security)

We recommend that you secure your webhook endpoint to prevent unauthorized access. Every webhook sent by SaaS Custom Domains includes a security token, both in the `token` query parameter and in the `X-SCD-Webhook-Token` request header. This token will look something like `19ca846829efbcafd41af16e04f123`. Requests are sent with the User-Agent `SaasCustomDomains.com`.

It is important to verify this token before processing the webhook. You can find the token in the webhook view in the SaaS Custom Domains application. Each webhook has its own unique token. Compare it in constant time, and reject the request if the token your app expects is empty, so a missing environment variable can't turn the check off.

## [Available Events](https://docs.saascustomdomains.com/webhooks#available-events)

Our webhook service provides real-time notifications for a range of domain and upstream events. Below you'll find the events you can subscribe to, along with a brief description of each.

All datetimes are in UTC and [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), e.g. `2024-02-27T15:23:54Z`.

### Domain Events

**Domain Created** \- A new domain has been created.

```json
{
   "event": "domain_created",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain Deleted** \- A domain has been deleted.

```json
{
   "event": "domain_deleted",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain DNS Proxy Record Valid** \- This event is fired when a CNAME record successfully points your custom domain to in.saascustomdomains.com, or A records are correctly set to our static IP addresses. Relevant only for non-wildcard custom domains.

```json
{
   "event": "domain_dns_proxy_record_valid",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain DNS Proxy Record Invalid** \- Alerts you when we can't find CNAME that points your custom domain to in.saascustomdomains.com, or A records pointing to our static IP addresses. Relevant only for non-wildcard custom domains.

```json
{
   "event": "domain_dns_proxy_record_invalid",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain DNS Challenge Record Valid** \- Notifies you when the DNS challenge records for your domain are correctly configured. Relevant only for custom domains using the DNS-01 challenge for verification.

```json
{
   "event": "domain_dns_challenge_record_valid",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain DNS Challenge Record Invalid** \- Triggered when we can't find domain's DNS challenge records. Relevant only for custom domains using the DNS-01 challenge for verification.

```json
{
   "event": "domain_dns_challenge_record_invalid",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**Domain Proxy Deployment Complete** \- Notify when the domain is fully deployed and ready to receive traffic in all regions.

```json
{
   "event": "domain_proxy_deployment_complete",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**TLS/SSL Certificate Issued** \- Indicates that a TLS/SSL certificate has been successfully issued for your domain.

```json
{
   "event": "tls_certificate_issued",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

**TLS/SSL Certificate Revoked** \- Occurs when a TLS/SSL certificate for your domain is revoked.

```json
{
   "event": "tls_certificate_revoked",
   "domain": {
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "domain_12345",
      "upstream_uuid": "upstream_12345",
      "host": "subdomain.apple.com",
      "challenge_type": "http01",
      "instructions_recipient": "steve@apple.com",
      "last_dns_check_at": "2024-02-27T15:31:27Z",
      "proxy_deployment_complete": true,
      "proxy_deployment_completed_at": "2024-02-27T15:35:12Z"
   }
}

```

### Upstream Events

**Upstream Created** \- Triggered when a new upstream is created.

```json
{
   "event": "upstream_created",
   "upstream":{
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "upstream_12345",
      "host": "app.domain.com",
      "port": "443",
      "tls": true,
      "bubble_io": false
  }
}

```

**Upstream Deleted** \- Triggered when an upstream is deleted.

```json
{
   "event": "upstream_deleted",
   "upstream":{
      "created_at": "2024-02-27T15:23:54Z",
      "uuid": "upstream_12345",
      "host": "app.domain.com",
      "port": "443",
      "tls": true,
      "bubble_io": false
  }
}

```

## [Timeouts and Retries](https://docs.saascustomdomains.com/webhooks#timeouts-and-retries)

We have a 5-second timeout for webhooks. Successful responses are those with a status code in the 2xx range. If we don't receive a successful response within 5 seconds, we consider the attempt failed and queue a retry.

We will retry sending the webhook up to 10 times. We use exponential backoff to determine the time between retries. The retries will be spaced out as follows:

| Retry | Next Retry Backoff | Total Waiting Time |
| ----- | ------------------ | ------------------ |
| 1     | 0d 0h 0m 20s       | 0d 0h 0m 20s       |
| 2     | 0d 0h 0m 26s       | 0d 0h 0m 46s       |
| 3     | 0d 0h 0m 46s       | 0d 0h 1m 32s       |
| 4     | 0d 0h 1m 56s       | 0d 0h 3m 28s       |
| 5     | 0d 0h 4m 56s       | 0d 0h 8m 24s       |
| 6     | 0d 0h 11m 10s      | 0d 0h 19m 34s      |
| 7     | 0d 0h 22m 26s      | 0d 0h 42m 0s       |
| 8     | 0d 0h 40m 56s      | 0d 1h 22m 56s      |
| 9     | 0d 1h 9m 16s       | 0d 2h 32m 12s      |
| 10    | 0d 1h 50m 26s      | 0d 4h 22m 38s      |

If the last retry fails too, the delivery has failed and we stop retrying it. The latest error is shown on the webhook's page in the dashboard.

After 10 consecutive failed deliveries, we disable the webhook automatically and email the account owner. A successful delivery, including a successful retry, resets the count. To turn the webhook back on, edit it in the dashboard.

---

<!-- Source: https://docs.saascustomdomains.com/rate-limits -->

# API Rate Limits

To keep the service stable for everyone, the SaaS Custom Domains API enforces rate limits. This page explains the limits and how a rate-limited response looks.

## [Understanding Rate Limits](https://docs.saascustomdomains.com/rate-limits#understanding-rate-limits)

The SaaS Custom Domains API allows up to 360 requests per minute per API token. This rate limit is designed to prevent abuse and ensure equitable access to our services for all users. If you require a higher rate limit, reach out to our support team.

The [Verify DNS records](https://docs.saascustomdomains.com/custom-domains#verify-dns-records) endpoint has its own limit of 5 requests per minute per API token. We re-check DNS records automatically after a custom domain is created, so polling this endpoint doesn't make verification any faster.

These limits apply to the API only, not to traffic on your custom domains. To limit traffic to your custom domains, use the [rate limit handler](https://docs.saascustomdomains.com/rules#rate-limit-handler) in Rules.

## [Exceeding Rate Limits](https://docs.saascustomdomains.com/rate-limits#exceeding-rate-limits)

If you exceed a rate limit, your additional requests will be rejected, and you will receive a `429: Too Many Requests` status code. Along with this, the response body will include the following information:

```json
{
  "error": "Rate Limit Exceeded",
  "error_code": "rate_limited"
}

```

## [Rate Limit Headers](https://docs.saascustomdomains.com/rate-limits#rate-limit-headers)

When your request is rate limited, i.e. when you get the `429: Too Many Requests` response, the response will also include additional headers that provide information about your current rate limit status:

* `X-RateLimit-Limit`: The maximum number of requests you can make per minute: `360`, or `5` for the Verify DNS records endpoint.
* `X-RateLimit-Remaining`: The number of requests you can still make in the current minute window.
* `X-RateLimit-Reset`: The timestamp (in Unix epoch time) when the rate limit will reset.

Wait until the time in `X-RateLimit-Reset` before you send more requests.

---

<!-- Source: https://docs.saascustomdomains.com/errors -->

# Errors

In this guide, we will talk about what happens when something goes wrong while you work with the API. Let's look at some status codes and error types you might encounter.

You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the error message to figure out what has gone wrong.

---

## [Status codes](https://docs.saascustomdomains.com/errors#status-codes)

Here is a list of the different categories of status codes returned by the SaaS Custom Domains API. Use these to understand if a request was successful.

* `2xx` ():  
A 2xx status code indicates a successful response.
* `4xx` ():  
A 4xx status code indicates a client error — mostly happens when the API token is not valid or the request is malformed.
* `5xx` ():  
A 5xx status code indicates a server error.

## [Error codes](https://docs.saascustomdomains.com/errors#error-codes)

Here is a list of the different error codes returned by the SaaS Custom Domains API. Use these to understand what has gone wrong.

These codes will be included in the JSON response body as `error_code`.

* `host_taken` ():  
This means the host you are trying to use for upstream or a custom domain is already taken. Make sure you are using a unique host.
* `host_invalid` ():  
This means the host you are trying to use for upstream or a custom domain is invalid. Make sure you are using a valid host.
* `host_blank` ():  
This means the host you are trying to use for upstream or a custom domain is not provided. Make sure you are providing a host.
* `not_found` ():  
This means the resource you are trying to access does not exist. Make sure you are using the correct resource UUID. The error message will also tell you what resource was not found.
* `endpoint_removed` ():  
This means the endpoint you are calling has been removed and will not come back. The response status is `410 Gone`.

---

## [Error types](https://docs.saascustomdomains.com/errors#error-types)

Whenever a request is unsuccessful, the Saas Custom Domains API will return an error response with a message. You can use this information to understand better what has gone wrong and how to fix it.

Here is a list of the usual errors that happen when working with SaaS Custom Domains API.

* `401: Unauthorized` ():  
This means the API token you are using is not valid. Make sure you are using the correct API token.
* `403: Forbidden` ():  
This means your account doesn't have access to the feature you are trying to use, for example analytics. Contact our support to enable it.
* `404: Not Found` ():  
This means the resource you are trying to access does not exist. Make sure you are using the correct resource UUID. The error message will also tell you what resource was not found.
* `410: Gone` ():  
This means the endpoint you are calling has been removed. See the error message for details.
* `400: Bad Request` ():  
This is unspecified client error that you shouldn't see often. It means the request you are trying to make is malformed. Make sure you are using the correct parameters and values.
* `5XX: Server Error` ():  
If there's an issue with our servers, you will be served 5xx status codes. Please try again later or reach out to our support.

### Example error response

```bash
{
  "error": "Upstream upstream_1234 not found.",
  "error_code": "not_found"
}

```

---

<!-- Source: https://docs.saascustomdomains.com/accounts -->

# Accounts

On this page, we'll dive into the accounts endpoints you can use to manage accounts programmatically. Currently, we only allow querying accounts via API.

## [The account model](https://docs.saascustomdomains.com/accounts#the-account-model)

The account model contains all the information about your accounts, such as their unique identifier, owner identifier etc.

### Properties

* `uuid` (string):  
Unique identifier for the account.
* `name` (string):  
Name of the account.
* `personal` (boolean):  
Whether the account is personal or not.
* `owner_uuid` (string):  
Unique identifier for the account owner.
* `created_at` (timestamp):  
Timestamp of when the account was created.
* `updated_at` (timestamp):  
Timestamp of when the account was last updated.

---

GET

/api/v1/accounts

## [List all accounts](https://docs.saascustomdomains.com/accounts#list-all-accounts)

This endpoint allows you to retrieve a list of all your accounts.

### Request

GET

/api/v1/accounts

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts \
  -H "Authorization: Bearer {token}"

```

### Response

```json
[
    {
        "uuid": "acc_d36c56a34db0",
        "name": "Rocky Balboa",
        "personal": true,
        "created_at": "2022-11-06T22:49:39.923+01:00",
        "updated_at": "2022-11-20T19:50:31.977+01:00",
        "owner_uuid": "u_33e1d4c2105b"
    },
    {
        "uuid": "acc_e47kk6a34db0",
        // ...
    },
    // ...
]

```

---

<!-- Source: https://docs.saascustomdomains.com/upstreams -->

# Upstreams

Upstreams are a core part of SaaS Custom Domains.

An upstream refers to the server to which the reverse proxy forwards requests. In your case, this is most likely your web application, e.g. app.mydomain.com.

On this page, we'll dive into the different upstream endpoints you can use to manage upstreams programmatically. We'll look at how to query, create, and delete upstreams.

There's no API endpoint for updating an upstream. To change its host, port, TLS setting or auth token, use Edit Upstream in the dashboard. Existing custom domains stay attached to the upstream.

## [The upstream model](https://docs.saascustomdomains.com/upstreams#the-upstream-model)

The upstream model contains all the information about your upstreams, such as their unique identifier, hostname, port, if upstream is expecting TLS traffic, if it is a Bubble.io app etc.

### Properties

* `uuid` (string):  
Unique identifier for the upstream.
* `host` (string):  
The host of the upstream. This is the host of your web application where you expect to receive requests, e.g. `app.mysaas.com`.
* `port` (integer):  
The port of the upstream. This is the port on which your web application listens. Default value is `443`.
* `tls` (boolean):  
Whether the upstream is using TLS. Default value is `true`. Turn it off and set the port to `80` if your upstream only serves plain HTTP (for example, a Heroku DNS target).
* `created_at` (timestamp):  
The date and time when the upstream was created.
* `updated_at` (timestamp):  
The date and time when the upstream was last updated.
* `bubble_io` (boolean):  
Whether the upstream is a Bubble.io app. Default value is `false`.
* `compression_enabled` (boolean):  
Whether automatic response compression is enabled for this upstream. When enabled, responses over 512 bytes are automatically compressed using gzip or zstd based on the client's Accept-Encoding header. Default value is `true`.
* `geocoding_enabled` (boolean):  
Whether geocoding headers are enabled for this upstream. When enabled, adds geographical headers (country, city, subdivision, postal code, continent, EU membership) based on the request's IP address to help with location-based content delivery and compliance. Default value is `false`.

### Geocoding Headers

When `geocoding_enabled` is set to `true`, the following headers are automatically added to requests forwarded to your upstream:

* `X-SaaS-GeoIp-Country-Code` \- Two-letter country code (e.g., "US")
* `X-SaaS-GeoIp-Country-Name` \- Full country name (e.g., "United States")
* `X-SaaS-GeoIp-City` \- City name (if available)
* `X-SaaS-GeoIp-Subdivision-1-Name` \- Primary subdivision name (e.g., a state in the US)
* `X-SaaS-GeoIp-Subdivision-1-Code` \- Primary subdivision code (e.g., "CA")
* `X-SaaS-GeoIp-Subdivision-2-Name` \- Secondary subdivision name (if applicable)
* `X-SaaS-GeoIp-Subdivision-2-Code` \- Secondary subdivision code (if applicable)
* `X-SaaS-GeoIp-Postal-Code` \- Postal code or ZIP code
* `X-SaaS-GeoIp-Country-Eu` \- Whether the country is in the European Union
* `X-SaaS-GeoIp-Continent-Code` \- Two-letter continent code (e.g., "NA")
* `X-SaaS-GeoIp-Continent-Name` \- Full name of the continent

---

GET

/api/v1/accounts/:account\_uuid/upstreams

## [List all upstreams for an account](https://docs.saascustomdomains.com/upstreams#list-all-upstreams-for-an-account)

This endpoint allows you to retrieve a paginated list of all your upstreams. By default, 20 upstreams are shown per page.

### Optional attributes

* `host` (string):  
Find upstream with the given host.
* `page` (integer):  
The page number to retrieve. Default value is `1`.
* `per_page` (integer):  
The number of items to retrieve per page. Default value is `20`.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams \
  -H "Authorization: Bearer {token}" \
  -d page=7

```

### Response

```json
{
  "pagination": {
    "page": 7,
    "count": 237
  },
  "data": [
    {
      "uuid": "upstream_1234567890",
      "host": "app.myapp.com",
      "port": 443,
      "tls": true,
      "auth_token": "abcdef123456",
      "bubble_io": false,
      "compression_enabled": false,
      "geocoding_enabled": false,
      "created_at": "2023-03-10T07:14:35.823-06:00",
      "updated_at": "2023-03-10T07:14:35.823-06:00"
    },
    {
      "uuid": "upstream_0987654321",
      // ...
    }
  ]
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams

## [Create an upstream](https://docs.saascustomdomains.com/upstreams#create-an-upstream)

This endpoint allows you to add a new upstream. To add an upstream, you must provide at least provide upstream's host.

### Required attributes

* `host` (string):  
The host of the upstream. This is the host of your web application where you expect to receive requests, e.g. `app.mysaas.com`.

### Optional attributes

* `tls` (boolean):  
Whether the upstream is using TLS. Default value is `true`. Turn it off and set the port to `80` if your upstream only serves plain HTTP (for example, a Heroku DNS target).
* `port` (integer):  
The port of the upstream. This is the port on which your web application listens. Default value is `443`.
* `bubble_io` (boolean):  
Whether the upstream is a Bubble.io app. Default value is `false`.
* `compression_enabled` (boolean):  
Whether automatic response compression is enabled for this upstream. When enabled, responses over 512 bytes are automatically compressed using gzip or zstd. Default value is `true`.
* `geocoding_enabled` (boolean):  
Whether geocoding headers are enabled for this upstream. When enabled, adds geographical headers based on the request's IP address. Default value is `false`.
* `auth_token` (string):  
A secret sent with every forwarded request in the `X-SaaS-Domains-Auth-Token` header, so your app can check the request came through SaaS Custom Domains. See [Handling forwarded requests](https://docs.saascustomdomains.com/handling-forwarded-requests). 1 to 64 letters, digits, dashes or underscores. If you leave it out when creating an upstream, a random 32-character token is generated.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams \
  -H "Authorization: Bearer {token}" \
  -d host="app.mysaas.com" \
  -d port=8888

```

### Response

```json
{
  "uuid": "upstream_180e8083",
  "host": "app.mysaas.com",
  "port": 8888,
  "tls": true,
  "auth_token": "abcdef123456",
  "bubble_io": false,
  "compression_enabled": true,
  "geocoding_enabled": false,
  "created_at": "2023-03-10T14:05:32.580+00:00",
  "updated_at": "2023-03-10T14:05:32.580+00:00",
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid

## [Retrieve an upstream](https://docs.saascustomdomains.com/upstreams#retrieve-an-upstream)

This endpoint allows you to retrieve an upstream by providing its UUID. Refer to [the list](https://docs.saascustomdomains.com/upstreams#the-upstream-model) at the top of this page to see which properties are included with upstream objects.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid \
      -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "upstream_180e8083",
  "host": "app.mysaas.com",
  "port": 8888,
  "tls": true,
  "auth_token": "abcdef123456",
  "bubble_io": false,
  "compression_enabled": true,
  "geocoding_enabled": false,
  "created_at": "2023-03-10T14:05:32.580+00:00",
  "updated_at": "2023-03-10T14:05:32.580+00:00",
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid

## [Delete an upstream](https://docs.saascustomdomains.com/upstreams#delete-an-upstream)

This endpoint allows you to delete upstreams. Note: This will also delete custom domains for that upstream.

### Request

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Upstream upstream_180e8083 (app.mysaas.com) deleted."
}

```

---

<!-- Source: https://docs.saascustomdomains.com/custom-domains -->

# Custom domains

On this page, we'll dive into the different custom domains endpoints you can use to manage custom domains programmatically. We'll look at how to query, create, verify, and delete custom domains.

Custom domains can't be updated, either with the API or in the dashboard. To change the host, challenge type, `redirect_to_www` or `prepend_path`, or to move a domain to another upstream, delete the domain and create it again. A domain can only belong to one upstream at a time. If the DNS records stay the same and you recreate the domain right away, traffic keeps working. The exception is Bubble settings (`bubble_target_path` and the `meta_*` fields), which you can change in the dashboard's Bubble Settings tab.

## [The custom domain model](https://docs.saascustomdomains.com/custom-domains#the-custom-domain-model)

The custom domain model contains all the information about your custom domains.

### Properties

* `uuid` (string):  
Unique identifier for the custom domain.
* `host` (string):  
The host of the custom domain. This is where your customer wants to access your application. This is the domain where your user will have to add a CNAME record.
* `prepend_path` (string):  
An optional path prefix that will be added to all requests forwarded to your upstream. For example, if set to `/sites/client1`, a request to `app.company.com/about` will be forwarded to your upstream as `/sites/client1/about`. It can only contain letters, numbers, dashes, underscores, colons, and forward slashes.
* `bubble_target_path` (string):  
Route visitors to a specific page in your Bubble app. For example, `stores/nike` will take a visitor to `https://yourapp.bubbleapps.io/stores/nike`. Leave blank to use your Bubble homepage. Only applicable when custom domain belongs to a Bubble.io upstream.
* `meta_title` (string):  
Sets the browser-tab title and headline shown by search engines and social previews. Keep it under 70 characters.
* `meta_description` (string):  
A brief description shown beneath the title in search results and link previews. Keep it under 160 characters.
* `meta_favicon_url` (string):  
URL of a 16 × 16 or 32 × 32 `.ico` or `.png` that will appear as the favicon in the browser tab.
* `meta_image_url` (string):  
URL of a larger `.png` or `.jpg` used as the Open Graph / social preview image.
* `challenge_type` (string):  
The type of challenge used to validate the certificate. It can be one of the following values: `http01`, `dns01`. Default value is `http01`. `http01` challenge will issue certificates on demand and `dns01` challenge will issue certificates when the `_acme-challenge` DNS records are found.  
The `dns01` challenge is useful if you want to use SaaS Custom Domains Shield, our product that puts Cloudflare DDoS protection and WAF in front of your custom domains.
* `redirect_to_www` (boolean):  
Indicates whether the custom domain should redirect to the `www` subdomain. This is useful if you want to redirect all traffic to the `www` subdomain. Default value is `false`.  
It can only be used on apex domains, not on subdomains or wildcards. The `www` domain gets its own certificate and counts as a second domain on your bill. Without it, `www.example.com` isn't served.
* `status` (string):  
The status of domain's proxy DNS records. This is the DNS record that points the custom domain to SaaS Custom Domains' proxy fleets. It can be one of the following values: `pending_check`, `dns_records_found`, `dns_records_not_found`, and `dns_check_unnecessary`.
* `tls_certificate_issued` (boolean):  
Indicates whether a TLS certificate has been issued for the custom domain.
* `acme_challenge_dns_record_status` (string):  
The status of the ACME challenge DNS record. It can be one of the following values: `valid`, `pending`, `not_found`. This property is only used for custom domains with the `dns01` certificate challenge type.
* `delegated_domain_control_validation_record_hostname` (string):  
The hostname of the ACME challenge DNS record. This property is only used for custom domains with the `dns01` certificate challenge type.
* `delegated_domain_control_validation_record_value` (string):  
The value of the ACME challenge DNS record. This property is only used for custom domains with the `dns01` certificate challenge type.
* `instructions_email_sent_at` (timestamp):  
The timestamp when the instructions email was sent to the user. Only applies if you decided to send the instruction email to the user when you created the custom domain.
* `instructions_recipient` (string):  
The email address of the user who was sent the instructions email. Only applies if you decided to send the instruction email to the user when you created the custom domain.
* `last_dns_check_at` (timestamp):  
The timestamp when the last DNS check was performed.
* `created_at` (timestamp):  
The date and time when the custom domain was created.
* `updated_at` (timestamp):  
The date and time when the custom domain was last updated.

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains

## [Retrieve all](https://docs.saascustomdomains.com/custom-domains#retrieve-all)

This endpoint allows you to retrieve a paginated list of all custom domains belonging to an upstream. By default, 20 custom domains are shown per page.

### Optional attributes

* `host` (string):  
Find custom domain with the given host.
* `page` (integer):  
The page number to retrieve. Default value is `1`.
* `per_page` (integer):  
The number of items to retrieve per page. Default value is `20`.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains \
  -H "Authorization: Bearer {token}" \
  -d page=7

```

### Response

```json
{
  "data": [
    {
      "uuid": "domain_52069e63",
      "host": "app.user.com",
      "prepend_path": null,
      "bubble_target_path": null,
      "meta_title": null,
      "meta_description": null,
      "meta_favicon_url": null,
      "meta_image_url": null,
      "created_at": "2022-11-14T13:19:42.193+01:00",
      "updated_at": "2023-03-11T08:32:27.239+01:00",
      "last_dns_check_at": "2023-03-11T08:32:27.216+01:00",
      "status": "dns_records_found",
      "tls_certificate_issued": true,
      "acme_challenge_dns_record_status": "valid",
      "challenge_type": "http01",
      "redirect_to_www": false,
      "instructions_recipient": null,
      "instructions_email_sent_at": null,
      "upstream_uuid": "upstream_3f43d84c",
      "delegated_domain_control_validation_record_hostname": null,
      "delegated_domain_control_validation_record_value": null
    },
    {
      "uuid": "domain_d3ba54e7",
      // ...
    },
    // ...
 "pagination": {
    "page": 7,
    "count": 322
 }
}

```

GET

/api/v1/custom\_domains

## [Retrieve all across accounts](https://docs.saascustomdomains.com/custom-domains#retrieve-all-across-accounts)

This endpoint returns custom domains from every account you belong to, so you can look a domain up by its host without knowing its account or upstream. By default, 20 custom domains are shown per page.

Each custom domain includes two extra properties: `account_uuid`, the account it belongs to, and `upstream_host`, the host of its upstream.

### Optional attributes

* `host` (string):  
Find the custom domain with this exact host. Case-insensitive.
* `account_uuid` (string):  
Only return custom domains in this account.
* `upstream_uuid` (string):  
Only return custom domains on this upstream.
* `status` (string):  
Only return custom domains with this DNS status: `pending_check`, `dns_records_found`, `dns_records_not_found` or `dns_check_unnecessary` (wildcard domains).
* `page` (integer):  
The page number to retrieve. Default value is `1`.
* `per_page` (integer):  
The number of items to retrieve per page. Default value is `20`.

### Request

GET

/api/v1/custom\_domains

```bash
curl -G https://app.saascustomdomains.com/api/v1/custom_domains \
  -H "Authorization: Bearer {token}" \
  -d host=app.user.com

```

### Response

```json
{
  "data": [
    {
      "uuid": "domain_52069e63",
      "host": "app.user.com",
      "prepend_path": null,
      "bubble_target_path": null,
      "meta_title": null,
      "meta_description": null,
      "meta_favicon_url": null,
      "meta_image_url": null,
      "created_at": "2022-11-14T13:19:42.193+01:00",
      "updated_at": "2023-03-11T08:32:27.239+01:00",
      "last_dns_check_at": "2023-03-11T08:32:27.216+01:00",
      "status": "dns_records_found",
      "tls_certificate_issued": true,
      "acme_challenge_dns_record_status": "valid",
      "challenge_type": "http01",
      "redirect_to_www": false,
      "instructions_recipient": null,
      "instructions_email_sent_at": null,
      "upstream_uuid": "upstream_3f43d84c",
      "delegated_domain_control_validation_record_hostname": null,
      "delegated_domain_control_validation_record_value": null,
      "account_uuid": "acc_d36c56a3",
      "upstream_host": "app.mysaas.com"
    }
  ],
  "pagination": {
    "page": 1,
    "count": 1
  }
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains

## [Create](https://docs.saascustomdomains.com/custom-domains#create)

This endpoint allows you to add a new custom domain to an upstream. Every custom domain you create counts toward your bill, whether or not its DNS records have been added.

### Required attributes

* `host` (string):  
The host of the custom domain. This is the domain where your user wants to access your application. Your user will have to add a CNAME record to this domain pointing it to `in.saascustomdomains.com`.

### Optional attributes

* `instructions_recipient` (string):  
The email address where the instructions to add the CNAME record will be sent. You need to have a valid and confirmed Sender Signature in your SaaS Custom Domain account in order to use this.
* `prepend_path` (string):  
An optional path prefix that will be added to all requests forwarded to your upstream. For example, if set to `/sites/client1`, a request to `app.company.com/about` will be forwarded to your upstream as `/sites/client1/about`. It can only contain letters, numbers, dashes, underscores, colons, and forward slashes.
* `challenge_type` (string):  
The challenge type for domain control verification. Possible values are `http01` and `dns01`. If not provided, the default value is `http01`. Wildcard domains must use `dns01`.
* `redirect_to_www` (boolean):  
Indicates whether the custom domain should redirect to the `www` subdomain. This is useful if you want to redirect all traffic to the `www` subdomain. Default value is `false`.  
It can only be used on apex domains, not on subdomains or wildcards. The `www` domain gets its own certificate and counts as a second domain on your bill, and it needs a CNAME record pointing to `in.saascustomdomains.com`. Without it, `www.example.com` isn't served.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains \
  -H "Authorization: Bearer {token}" \
  -d host="app.user.com"

```

### Response

```json
{
  "uuid": "domain_4e30e873",
  "host": "app.user.com",
  "prepend_path": null,
  "bubble_target_path": null,
  "meta_title": null,
  "meta_description": null,
  "meta_favicon_url": null,
  "meta_image_url": null,
  "created_at": "2023-03-11T15:38:12.550+01:00",
  "updated_at": "2023-03-11T15:38:12.550+01:00",
  "last_dns_check_at": null,
  "status": "pending_check",
  "tls_certificate_issued": false,
  "acme_challenge_dns_record_status": "valid",
  "challenge_type": "http01",
  "redirect_to_www": false,
  "instructions_recipient": null,
  "instructions_email_sent_at": null,
  "upstream_uuid": "upstream_3f43d84c",
  "delegated_domain_control_validation_record_hostname": null,
  "delegated_domain_control_validation_record_value": null
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid

## [Retrieve](https://docs.saascustomdomains.com/custom-domains#retrieve)

This endpoint allows you to retrieve a custom domain by providing its UUID. Refer to [the list](https://docs.saascustomdomains.com/custom-domains#the-custom-domain-model) at the top of this page to see which properties are included with custom domain objects.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains/:domain_uuid \
      -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "domain_3cdfe9a4",
  "host": "*.app.com",
  "prepend_path": null,
  "bubble_target_path": null,
  "meta_title": null,
  "meta_description": null,
  "meta_favicon_url": null,
  "meta_image_url": null,
  "created_at": "2023-03-11T15:38:12.550+01:00",
  "updated_at": "2023-03-11T15:43:16.672+01:00",
  "last_dns_check_at": "2023-03-11T15:43:16.667+01:00",
  "status": "dns_check_unnecessary",
  "tls_certificate_issued": false,
  "acme_challenge_dns_record_status": "pending",
  "challenge_type": "dns01",
  "redirect_to_www": false,
  "instructions_recipient": null,
  "instructions_email_sent_at": null,
  "upstream_uuid": "upstream_3f43d84c",
  "delegated_domain_control_validation_record_hostname": "_acme-challenge.testhost.app.com",
  "delegated_domain_control_validation_record_value": "_acme-challenge.testhost.app.com.challenges.saascustomdomains.com"
}

```

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/dns\_records

## [Retrieve DNS records](https://docs.saascustomdomains.com/custom-domains#retrieve-dns-records)

This endpoint returns the DNS records your customer needs to add for a custom domain. They're the same records the dashboard shows. Each record has a `type`, a `name` (the full hostname), a `value` and a `purpose`:

* `proxy` records point the domain to SaaS Custom Domains. Subdomains and wildcards get a CNAME to your CNAME target. Root domains using the `http01` challenge get two A records instead, because not every DNS provider allows a CNAME at the root. Domains with `redirect_to_www` also get a CNAME for their `www` subdomain.
* `acme_challenge` records are only returned for the `dns01` challenge type. They let us issue and renew the certificate, so they must stay in place.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/dns\_records

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains/:domain_uuid/dns_records \
      -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "data": [
    {
      "type": "CNAME",
      "name": "*.app.com",
      "value": "in.saascustomdomains.com",
      "purpose": "proxy"
    },
    {
      "type": "CNAME",
      "name": "_acme-challenge.app.com",
      "value": "_acme-challenge.app.com.challenges.saascustomdomains.com",
      "purpose": "acme_challenge"
    }
  ]
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid

## [Delete](https://docs.saascustomdomains.com/custom-domains#delete)

This endpoint allows you to delete custom domains.

### Request

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains/:domain_uuid \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Custom domain domain_4e30e873 (app.user.com) deleted."
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/verify\_dns\_records

## [Verify DNS records](https://docs.saascustomdomains.com/custom-domains#verify-dns-records)

This endpoint allows you verify DNS records for a custom domain.

Once the domain has been created, SaaS Custom Domains automatically checks for the presence of the required DNS records.

We check the DNS records immediately, then at 15, 30 and 45 seconds, then every minute for the first 10 minutes, and then every 5 minutes up to 60 minutes.

After that, we check DNS records once every 3 hours. If you need to trigger the check manually or just check if the DNS records are valid, you can use this endpoint. It's limited to 5 requests per minute per API token (see [Rate limits](https://docs.saascustomdomains.com/rate-limits)), and calling it more often doesn't speed up verification.

### Response

Returns the host of the custom domain and the status of the DNS records. Status can be one of the following values: `valid`, `pending`, and `invalid`.

If the status is `valid`, it means we found the required DNS records and the custom domain is ready to be used.

If the status is `pending`, it means we haven't finished checking the DNS records yet. If the status is `invalid`, it means we couldn't find the required DNS records and we'll check again later.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/verify\_dns\_records

```bash
curl -X POST https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains/:domain_uuid/verify_dns_records \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "DNS records verification complete.",
  "dns_status": "valid",
  "host": "app.example.com"
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/purge\_http\_cache

## [Purge HTTP cache](https://docs.saascustomdomains.com/custom-domains#purge-http-cache)

This endpoint allows you to initiate an HTTP cache purge for a custom domain. This is useful if you have made changes to your upstream application and want to ensure that the latest content is served to your users.

Caching is a paid add-on (Caching, enabled in the dashboard), so purging only matters when caching is on for the domain.

**Note:** This is an asynchronous process and it may take a few seconds for the cache purge to fully complete across all edge locations.

### Response

Returns a message indicating whether the cache purge was successfully initiated.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:domain\_uuid/purge\_http\_cache

```bash
curl -X POST https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/upstreams/:upstream_uuid/custom_domains/:domain_uuid/purge_http_cache \
  -H "Authorization: Bearer {token}"

```

### Response (Success)

```json
{
  "message": "HTTP cache purge initiated for domain app.example.com."
}

```

### Response (Error)

```json
{
  "error": "Failed to initiate HTTP cache purge for domain app.example.com."
}

```

---

<!-- Source: https://docs.saascustomdomains.com/rules -->

# Rules

On this page, we'll dive into the different rule endpoints you can use to manage rules programmatically. Rules can be attached either to an upstream or to a single custom domain. We'll look at how to query, create, update, and delete rules, as well as how to reorder them.

## [The rule model](https://docs.saascustomdomains.com/rules#the-rule-model)

The rule model contains all the information about your rules. Rules allow you to define custom behavior based on request patterns. Each rule consists of matchers (conditions) and handlers (actions). Rules are evaluated in order based on their position. Every rule whose matchers match is applied, until a handler ends the request (for example a static response, a redirect, or an exceeded rate limit). A rule can be scoped either to an upstream, in which case it applies to every custom domain on that upstream, or to a single custom domain. See [Rule scopes](https://docs.saascustomdomains.com/rules#rule-scopes) for details.

### Properties

* `uuid` (string):  
Unique identifier for the rule.
* `name` (string):  
Human-readable name for the rule.
* `active` (boolean):  
Whether the rule is currently active.
* `position` (integer):  
Position of the rule in the evaluation order (lower numbers are evaluated first).
* `matchers` (array):  
An array of conditions that determine when the rule should be applied. Matchers define the criteria for matching incoming requests based on path, HTTP method, headers, query parameters, country, or remote IP address. Multiple matchers are combined with AND logic, meaning all conditions must be met for the rule to match. A rule can have at most one matcher of each type.
* `handlers` (array):  
An array of actions to execute when the rule matches. Handlers define what happens to the request or response when all matchers are satisfied. This can include rewriting URLs, returning static responses, modifying response headers, replacing content in the response body, redirecting, or rate limiting requests.
* `scope` (string):  
The resource the rule is attached to. Either `upstream` (the rule applies to every custom domain on the upstream) or `domain` (the rule applies to a single custom domain).
* `upstream_uuid` (string):  
UUID of the upstream this rule belongs to. For domain-scoped rules, this is the UUID of the upstream that the custom domain belongs to.
* `custom_domain_uuid` (string):  
UUID of the custom domain this rule belongs to. This is `null` for upstream-scoped rules.
* `account_uuid` (string):  
UUID of the account that owns this rule.
* `created_at` (timestamp):  
The date and time when the rule was created.
* `updated_at` (timestamp):  
The date and time when the rule was last updated.

## [Rule scopes](https://docs.saascustomdomains.com/rules#rule-scopes)

Rules can be attached at two different scopes:

* **Upstream rules** are attached to an upstream and apply to every custom domain on that upstream. They are managed under the `/upstreams/:upstream_uuid/rules` path.
* **Domain rules** are attached to a single custom domain and apply only to that domain. They are managed under the `/upstreams/:upstream_uuid/custom_domains/:custom_domain_uuid/rules` path.

Upstream rules are evaluated before a domain's own rules, so a matching upstream rule takes precedence. Within each scope, rules are evaluated in order based on their position.

The rule model, matcher types, and handler types are identical for both scopes; only the endpoint paths differ.

## [Matcher Types](https://docs.saascustomdomains.com/rules#matcher-types)

Rules can include the following matcher types:

### Path Matcher

Match requests based on the URL path component.

* `type` (string):  
Must be set to "path".
* `value` (string):  
The path value to match against.
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Exact path match
  * `is_not` \- Path does not match exactly
  * `starts_with` \- Path starts with the value
  * `ends_with` \- Path ends with the value
  * `contains` \- Path contains the value
  * `does_not_contain` \- Path does not contain the value

```json
{
  "type": "path",
  "value": "/blog",
  "operator": "starts_with"
}

```

### Method Matcher

Match requests based on the HTTP method (GET, POST, PUT, DELETE, etc.).

* `type` (string):  
Must be set to "method".
* `value` (string):  
The HTTP method to match against (e.g., "GET", "POST").
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Method matches exactly
  * `is_not` \- Method does not match
  * `has_any_value` \- Any method
  * `is_unknown` \- No method (rare/impossible in HTTP)

```json
{
  "type": "method",
  "value": "POST",
  "operator": "is"
}

```

### Header Matcher

Match requests based on the presence or value of HTTP headers.

* `type` (string):  
Must be set to "header".
* `header_params` (array):  
Array of header parameters to match against.

Each header parameter has the following properties:

* `name` (string):  
The name of the header.
* `value` (string):  
The value to match against.
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Header value matches exactly
  * `is_not` \- Header value does not match
  * `starts_with` \- Header value starts with the value
  * `ends_with` \- Header value ends with the value
  * `contains` \- Header value contains the value
  * `does_not_contain` \- Header value does not contain the value
  * `has_any_value` \- Header exists (any value)
  * `is_unknown` \- Header does not exist

```json
{
  "type": "header",
  "header_params": [
    {
      "name": "User-Agent",
      "value": "Mozilla",
      "operator": "contains"
    },
    {
      "name": "Accept-Language",
      "value": "en",
      "operator": "starts_with"
    }
  ]
}

```

### Query Matcher

Match requests based on query string parameters in the URL.

* `type` (string):  
Must be set to "query".
* `query_params` (array):  
Array of query parameters to match against.

Each query parameter has the following properties:

* `key` (string):  
The name of the query parameter.
* `value` (string):  
The value to match against.
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Query parameter value matches exactly
  * `is_not` \- Query parameter value does not match
  * `starts_with` \- Query parameter value starts with the value
  * `ends_with` \- Query parameter value ends with the value
  * `contains` \- Query parameter value contains the value
  * `does_not_contain` \- Query parameter value does not contain the value
  * `has_any_value` \- Query parameter exists (any value)
  * `is_unknown` \- Query parameter does not exist

```json
{
  "type": "query",
  "query_params": [
    {
      "key": "version",
      "value": "v2",
      "operator": "is"
    },
    {
      "key": "debug",
      "operator": "has_any_value"
    }
  ]
}

```

### Country Matcher

Match requests based on the client's country code, determined through GeoIP lookup.

* `type` (string):  
Must be set to "country".
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Client's country code matches one of the specified codes
  * `is_not` \- Client's country code does not match any of the specified codes
  * `is_unknown` \- Client's country code could not be determined
  * `has_any_value` \- Client's country code exists and is not unknown
* `country_codes` (array):  
Array of ISO 3166-1 alpha-2 country codes to match against (e.g., "US", "GB", "FR"). Required for `is` and `is_not` operators.

```json
{
  "type": "country",
  "operator": "is",
  "country_codes": ["US", "CA"]
}

```

### Remote IP Matcher

Match requests based on the visitor's IP address.

* `type` (string):  
Must be set to "remote\_ip".
* `operator` (string):  
The comparison operator to use. Possible values:

  * `is` \- Visitor's IP address is in one of the specified ranges
  * `is_not` \- Visitor's IP address is not in any of the specified ranges
* `ranges` (array):  
Array of IPv4 or IPv6 addresses or CIDR ranges to match against (e.g., "203.0.113.7", "203.0.113.0/24", "2001:db8::/32"). At least one is required.

```json
{
  "type": "remote_ip",
  "operator": "is_not",
  "ranges": ["203.0.113.0/24", "2001:db8::/32"]
}

```

## [Handler Types](https://docs.saascustomdomains.com/rules#handler-types)

Rules can include the following handler types:

### Static Response Handler

Return a custom HTTP response without forwarding the request to the upstream server.

* `type` (string):  
Must be set to "static\_response".
* `status_code` (integer):  
The HTTP status code to return.
* `body` (string):  
The response body to return.

```json
{
  "type": "static_response",
  "status_code": 200,
  "body": "<!DOCTYPE html><html><body><h1>Hello World!</h1></body></html>"
}

```

### Rewrite Handler

Modify the request URL before forwarding it to the upstream server. To send visitors to a different host, use the [Redirect handler](https://docs.saascustomdomains.com/rules#redirect-handler). To serve a domain from a different backend, make that backend the domain's upstream.

* `type` (string):  
Must be set to "rewrite".
* `uri` (string):  
The path to rewrite the request to. It must start with `/` and can't include a scheme or host. You can use placeholders in your rewrite URI (see below).

#### Rewrite URI Placeholders

The following placeholders can be used in the rewrite URI:

`{http.request.uri}` The full request URI

`{http.request.uri.path}` The path component (e.g. `/old-path`)

`{http.request.uri.path.dir}` The directory (excluding the filename)

`{http.request.uri.path.file}` The filename (excluding the directory)

`{http.request.uri.query}` The query string (without `?`)

```json
{
  "type": "rewrite",
  "uri": "/blog{http.request.uri.path}?source=proxy&{http.request.uri.query}"
}

```

### Replace Response Handler

Modify the response body content by replacing text patterns.

* `type` (string):  
Must be set to "replace\_response".
* `replacements` (array):  
Array of replacements to apply to the response body.

Each replacement has the following properties:

* `search` (string):  
The string to search for.
* `replace` (string):  
The string to replace matches with.

```json
{
  "type": "replace_response",
  "replacements": [
    {
      "search": "example.com",
      "replace": "customdomain.com"
    },
    {
      "search": "Original Content",
      "replace": "Modified Content"
    }
  ]
}

```

### Set Response Header Handler

Add or modify headers in the HTTP response.

* `type` (string):  
Must be set to "set\_response\_header".
* `headers` (array):  
Array of headers to set in the response.

Each header has the following properties:

* `name` (string):  
The name of the header.
* `value` (string):  
The value to set for the header.

```json
{
  "type": "set_response_header",
  "headers": [
    {
      "name": "X-Custom-Header",
      "value": "custom-value"
    },
    {
      "name": "Cache-Control",
      "value": "max-age=3600"
    }
  ]
}

```

### Delete Response Header Handler

Remove specific headers from the HTTP response.

* `type` (string):  
Must be set to "delete\_response\_header".
* `headers` (array):  
Array of headers to delete from the response.

Each header has the following properties:

* `name` (string):  
The name of the header to delete.

```json
{
  "type": "delete_response_header",
  "headers": [
    {
      "name": "Server"
    },
    {
      "name": "X-Powered-By"
    }
  ]
}

```

### Redirect Handler

Redirect the request to a different URL with a specified HTTP status code.

* `type` (string):  
Must be set to "redirect".
* `location` (string):  
The URL to redirect to. Must be a valid URL starting with http:// or https://. You can use placeholders in your redirect location (see below).
* `status_code` (integer):  
The HTTP status code to use for the redirect. Must be either 301 (permanent) or 302 (temporary).

The following placeholders can be used in the Redirect Location URI:

`{http.request.uri}` The full request URI

`{http.request.uri.path}` The path component (e.g. `/old-path`)

`{http.request.uri.path.dir}` The directory (excluding the filename)

`{http.request.uri.path.file}` The filename (excluding the directory)

`{http.request.uri.query}` The query string (without `?`)

```json
{
  "type": "redirect",
  "location": "https://new-location.example.com{http.request.uri.path}?source=redirect&{http.request.uri.query}",
  "status_code": 301
}

```

### Rate Limit Handler

Limit how many matching requests are let through in a time window. Requests over the limit get a `429 Too Many Requests` response. Requests under the limit continue to the next rules and your upstream.

* `type` (string):  
Must be set to "rate\_limit".
* `limit_value` (integer):  
The maximum number of requests allowed per window. Must be a positive integer.
* `window_value` (integer):  
The length of the window, in `window_unit`s.
* `window_unit` (string):  
The unit of the window. Possible values:

  * `m` \- Minutes, with a `window_value` from 1 to 60\. Available to every account.
  * `s` \- Seconds, from 30 to 60\. Enabled on request; contact support.
  * `h` \- Hours, from 1 to 24\. Enabled on request; contact support.
  * `d` \- Days, with a `window_value` of 1\. Enabled on request; contact support.
* `key_parts` (array):  
Decides how requests are counted. With an empty array, all matching requests share one counter (a fixed bucket). Otherwise each unique combination of the values below gets its own counter. Each key part is an object with a `type`:

  * `ip` \- The visitor's IP address
  * `domain` \- The requested host
  * `path` \- The request path
  * `query` \- The query string
  * `header` \- The value of a request header. Also set `name` to the header name, e.g. `{"type": "header", "name": "Authorization"}`. Header names must be valid and unique within the handler.

On an upstream rule, a counter is shared by every custom domain on the upstream. Add a `domain` key part to count each domain separately.

```json
{
  "type": "rate_limit",
  "limit_value": 100,
  "window_value": 1,
  "window_unit": "m",
  "key_parts": [{ "type": "ip" }, { "type": "domain" }]
}

```

For example, this rule allows 10 login attempts per minute from each IP address:

```json
{
  "name": "Limit login attempts",
  "matchers": [
    { "type": "path", "value": "/login", "operator": "is" },
    { "type": "method", "value": "POST", "operator": "is" }
  ],
  "handlers": [
    {
      "type": "rate_limit",
      "limit_value": 10,
      "window_value": 1,
      "window_unit": "m",
      "key_parts": [{ "type": "ip" }]
    }
  ]
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules

## [List Upstream Rules](https://docs.saascustomdomains.com/rules#list-upstream-rules)

This endpoint allows you to retrieve a paginated list of all rules for an upstream.

### Optional attributes

* `active` (boolean):  
Filter rules by active status.
* `sort` (string):  
Field to sort by (e.g., `position`, `name`, `created_at`).
* `direction` (string):  
Sort direction (`asc` or `desc`).
* `page` (integer):  
Page number for pagination.
* `per_page` (integer):  
Number of rules per page. Defaults to 20, maximum 100.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules \
  -H "Authorization: Bearer {token}" \
  -d active=true

```

### Response

```json
{
  "data": [
    {
      "uuid": "rule_01234abc",
      "name": "Block legacy API",
      "active": true,
      "position": 1,
      "matchers": [
        {
          "type": "path",
          "value": "/api/v1",
          "operator": "starts_with"
        }
      ],
      "handlers": [
        {
          "type": "static_response",
          "status_code": 410,
          "body": "Gone"
        }
      ],
      "scope": "upstream",
      "upstream_uuid": "upstream_456def",
      "custom_domain_uuid": null,
      "account_uuid": "acc_123abc",
      "created_at": "2023-01-01T12:00:00Z",
      "updated_at": "2023-01-01T12:00:00Z"
    },
    {
      "uuid": "rule_56789def",
      // ...
    }
  ],
  "pagination": {
    "page": 1,
    "count": 10,
    "items": 25,
    "pages": 1
  }
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

## [Get an Upstream Rule](https://docs.saascustomdomains.com/rules#get-an-upstream-rule)

This endpoint allows you to retrieve a single upstream rule by its UUID.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules/rule_01234abc \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Block legacy API",
  "active": true,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/api/v1",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "static_response",
      "status_code": 410,
      "body": "Gone"
    }
  ],
  "scope": "upstream",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": null,
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:00:00Z"
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules

## [Create an Upstream Rule](https://docs.saascustomdomains.com/rules#create-an-upstream-rule)

This endpoint allows you to create a new rule for an upstream. The rule will be assigned the next available position and will apply to every custom domain on the upstream.

### Required attributes

* `name` (string):  
Human-readable name for the rule.
* `matchers` (array):  
At least one matcher must be provided.
* `handlers` (array):  
At least one handler must be provided.

### Optional attributes

* `active` (boolean):  
Whether the rule is active. Default is `true`.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Block legacy API",
    "active": true,
    "matchers": [
      {
        "type": "path",
        "value": "/api/v1",
        "operator": "starts_with"
      }
    ],
    "handlers": [
      {
        "type": "static_response",
        "status_code": 410,
        "body": "Gone"
      }
    ]
  }'

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Block legacy API",
  "active": true,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/api/v1",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "static_response",
      "status_code": 410,
      "body": "Gone"
    }
  ],
  "scope": "upstream",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": null,
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:00:00Z"
}

```

---

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

## [Update an Upstream Rule](https://docs.saascustomdomains.com/rules#update-an-upstream-rule)

This endpoint allows you to update an existing upstream rule.

### Optional attributes

* `name` (string):  
Human-readable name for the rule.
* `active` (boolean):  
Whether the rule is active.
* `matchers` (array):  
Conditions that determine when the rule should be applied.
* `handlers` (array):  
Actions to take when the rule matches.

### Request

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

```bash
curl -X PATCH https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules/rule_01234abc \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated rule name",
    "active": false
  }'

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Updated rule name",
  "active": false,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/api/v1",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "static_response",
      "status_code": 410,
      "body": "Gone"
    }
  ],
  "scope": "upstream",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": null,
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:30:00Z"
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

## [Delete an Upstream Rule](https://docs.saascustomdomains.com/rules#delete-an-upstream-rule)

This endpoint allows you to delete an upstream rule.

### Request

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/:uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules/rule_01234abc \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Rule rule_01234abc (Block legacy API) deleted."
}

```

---

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/update\_positions

## [Update Upstream Rule Positions](https://docs.saascustomdomains.com/rules#update-upstream-rule-positions)

This endpoint allows you to update the positions of multiple upstream rules at once.

### Required attributes

* `rule_positions` (array):  
Array of rule position updates.

Each rule position update has the following properties:

* `uuid` (string):  
The UUID of the rule.
* `position` (integer):  
The new position for the rule.

### Request

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/rules/update\_positions

```bash
curl -X PATCH https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/rules/update_positions \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_positions": [
      {
        "uuid": "rule_01234abc",
        "position": 2
      },
      {
        "uuid": "rule_56789def",
        "position": 1
      }
    ]
  }'

```

### Response

```json
{
  "message": "Rule positions updated successfully."
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules

## [List Domain Rules](https://docs.saascustomdomains.com/rules#list-domain-rules)

This endpoint allows you to retrieve a paginated list of all rules for a custom domain.

### Optional attributes

* `active` (boolean):  
Filter rules by active status.
* `sort` (string):  
Field to sort by (e.g., `position`, `name`, `created_at`).
* `direction` (string):  
Sort direction (`asc` or `desc`).
* `page` (integer):  
Page number for pagination.
* `per_page` (integer):  
Number of rules per page. Defaults to 20, maximum 100.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules \
  -H "Authorization: Bearer {token}" \
  -d active=true

```

### Response

```json
{
  "data": [
    {
      "uuid": "rule_01234abc",
      "name": "Rewrite blog path",
      "active": true,
      "position": 1,
      "matchers": [
        {
          "type": "path",
          "value": "/blog",
          "operator": "starts_with"
        }
      ],
      "handlers": [
        {
          "type": "rewrite",
          "uri": "/sites/acme{http.request.uri.path}"
        }
      ],
      "scope": "domain",
      "upstream_uuid": "upstream_456def",
      "custom_domain_uuid": "domain_789ghi",
      "account_uuid": "acc_123abc",
      "created_at": "2023-01-01T12:00:00Z",
      "updated_at": "2023-01-01T12:00:00Z"
    },
    {
      "uuid": "rule_56789def",
      // ...
    }
  ],
  "pagination": {
    "page": 1,
    "count": 10,
    "items": 25,
    "pages": 1
  }
}

```

---

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

## [Get a Domain Rule](https://docs.saascustomdomains.com/rules#get-a-domain-rule)

This endpoint allows you to retrieve a single rule by its UUID.

### Request

GET

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules/rule_01234abc \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Rewrite blog path",
  "active": true,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/blog",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "rewrite",
      "uri": "/sites/acme{http.request.uri.path}"
    }
  ],
  "scope": "domain",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": "domain_789ghi",
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:00:00Z"
}

```

---

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules

## [Create a Domain Rule](https://docs.saascustomdomains.com/rules#create-a-domain-rule)

This endpoint allows you to create a new rule for a custom domain. The rule will be assigned the next available position.

### Required attributes

* `name` (string):  
Human-readable name for the rule.
* `matchers` (array):  
At least one matcher must be provided.
* `handlers` (array):  
At least one handler must be provided.

### Optional attributes

* `active` (boolean):  
Whether the rule is active. Default is `true`.

### Request

POST

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Rewrite blog path",
    "active": true,
    "matchers": [
      {
        "type": "path",
        "value": "/blog",
        "operator": "starts_with"
      }
    ],
    "handlers": [
      {
        "type": "rewrite",
        "uri": "/sites/acme{http.request.uri.path}"
      }
    ]
  }'

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Rewrite blog path",
  "active": true,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/blog",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "rewrite",
      "uri": "/sites/acme{http.request.uri.path}"
    }
  ],
  "scope": "domain",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": "domain_789ghi",
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:00:00Z"
}

```

---

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

## [Update a Domain Rule](https://docs.saascustomdomains.com/rules#update-a-domain-rule)

This endpoint allows you to update an existing rule.

### Optional attributes

* `name` (string):  
Human-readable name for the rule.
* `active` (boolean):  
Whether the rule is active.
* `matchers` (array):  
Conditions that determine when the rule should be applied.
* `handlers` (array):  
Actions to take when the rule matches.

### Request

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

```bash
curl -X PATCH https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules/rule_01234abc \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated rule name",
    "active": false
  }'

```

### Response

```json
{
  "uuid": "rule_01234abc",
  "name": "Updated rule name",
  "active": false,
  "position": 1,
  "matchers": [
    {
      "type": "path",
      "value": "/blog",
      "operator": "starts_with"
    }
  ],
  "handlers": [
    {
      "type": "rewrite",
      "uri": "/sites/acme{http.request.uri.path}"
    }
  ],
  "scope": "domain",
  "upstream_uuid": "upstream_456def",
  "custom_domain_uuid": "domain_789ghi",
  "account_uuid": "acc_123abc",
  "created_at": "2023-01-01T12:00:00Z",
  "updated_at": "2023-01-01T12:30:00Z"
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

## [Delete a Domain Rule](https://docs.saascustomdomains.com/rules#delete-a-domain-rule)

This endpoint allows you to delete a rule.

### Request

DELETE

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/:uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules/rule_01234abc \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Rule rule_01234abc (Rewrite blog path) deleted."
}

```

---

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/update\_positions

## [Update Domain Rule Positions](https://docs.saascustomdomains.com/rules#update-domain-rule-positions)

This endpoint allows you to update the positions of multiple rules at once.

### Required attributes

* `rule_positions` (array):  
Array of rule position updates.

Each rule position update has the following properties:

* `uuid` (string):  
The UUID of the rule.
* `position` (integer):  
The new position for the rule.

### Request

PATCH

/api/v1/accounts/:account\_uuid/upstreams/:upstream\_uuid/custom\_domains/:custom\_domain\_uuid/rules/update\_positions

```bash
curl -X PATCH https://app.saascustomdomains.com/api/v1/accounts/acc_123abc/upstreams/upstream_456def/custom_domains/domain_789ghi/rules/update_positions \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_positions": [
      {
        "uuid": "rule_01234abc",
        "position": 2
      },
      {
        "uuid": "rule_56789def",
        "position": 1
      }
    ]
  }'

```

### Response

```json
{
  "message": "Rule positions updated successfully."
}

```

---

## [Error Responses](https://docs.saascustomdomains.com/rules#error-responses)

When an error occurs, the API will return an appropriate HTTP status code and a JSON response with error details:

```json
{
  "error": "Rule not found.",
  "error_code": "not_found"
}

```

Common error codes:

* `not_found` \- The requested resource was not found
* `feature_not_enabled` \- The rules feature is not enabled for this account (upstream rules additionally require the upstream rules feature to be enabled)
* `invalid_position` \- The requested position is invalid
* Various validation errors (e.g., `name_blank`, `matchers_invalid`)

---

<!-- Source: https://docs.saascustomdomains.com/custom-certificates -->

# Custom Certificates

On this page, we'll dive into the different custom certificate endpoints you can use to manage custom certificates programmatically. We'll look at how to query, create, update, and delete custom certificates.

## [The custom certificate model](https://docs.saascustomdomains.com/custom-certificates#the-custom-certificate-model)

The custom certificate model contains all the information about your custom certificates.

### Properties

* `uuid` (string):  
Unique identifier for the custom certificate.
* `expires_at` (timestamp):  
The expiration date and time of the certificate.
* `not_before` (timestamp):  
The date and time before which the certificate is not valid.
* `fingerprint` (string):  
The fingerprint of the certificate, used for verification.
* `issuer` (string):  
The issuer of the certificate, containing information about the certificate authority.
* `public_key_sha` (string):  
The SHA-1 hash of the public key.
* `sans` (array):  
An array of Subject Alternative Names (SANs) included in the certificate.
* `serial_number` (string):  
The serial number of the certificate.
* `signature_algorithm` (string):  
The algorithm used to sign the certificate.
* `subject` (string):  
The subject of the certificate, containing information about the entity the certificate belongs to.
* `created_at` (timestamp):  
The date and time when the certificate was created in SaaS Custom Domains.

---

GET

/api/v1/accounts/:account\_uuid/custom\_certificates

## [Retrieve all](https://docs.saascustomdomains.com/custom-certificates#retrieve-all)

This endpoint allows you to retrieve a paginated list of all custom certificates belonging to an account. By default, 20 custom certificates are shown per page.

### Optional attributes

* `page` (integer):  
The page number to retrieve. Default value is `1`.
* `per_page` (integer):  
The number of items to retrieve per page. Default value is `20`.

### Request

GET

/api/v1/accounts/:account\_uuid/custom\_certificates

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/custom_certificates \
  -H "Authorization: Bearer {token}" \
  -d page=7

```

### Response

```json
{
  "data": [
    {
      "uuid": "cert_1",
      "expires_at": "2098-12-31T18:00:00.000-06:00",
      "not_before": "2019-12-31T18:00:00.000-06:00",
      "fingerprint": "abcdef1234567890abcdef1234567890abcdef12",
      "issuer": "CN=Example CA, O=Example Org, C=US",
      "public_key_sha": "abcdef1234567890abcdef1234567890abcdef12",
      "sans": ["example.com", "www.example.com"],
      "serial_number": "1234567890",
      "signature_algorithm": "sha256WithRSAEncryption",
      "subject": "CN=example.com, O=Example Org, C=US",
      "created_at": "2024-07-19T04:48:27.000-05:00"
    },
    {
      "uuid": "cert_2",
      // ...
    },
    // ...
  ],
  "pagination": {
    "page": 7,
    "count": 322
  }
}

```

---

GET

/api/v1/accounts/:account\_uuid/custom\_certificates/:certificate\_uuid

## [Retrieve](https://docs.saascustomdomains.com/custom-certificates#retrieve)

This endpoint allows you to retrieve a custom certificate by providing its UUID. Refer to [the list](https://docs.saascustomdomains.com/custom-certificates#the-custom-certificate-model) at the top of this page to see which properties are included with custom certificate objects.

### Request

GET

/api/v1/accounts/:account\_uuid/custom\_certificates/:certificate\_uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/custom_certificates/:certificate_uuid \
      -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "cert_1",
  "expires_at": "2098-12-31T18:00:00.000-06:00",
  "not_before": "2019-12-31T18:00:00.000-06:00",
  "fingerprint": "abcdef1234567890abcdef1234567890abcdef12",
  "issuer": "CN=Example CA, O=Example Org, C=US",
  "public_key_sha": "abcdef1234567890abcdef1234567890abcdef12",
  "sans": ["example.com", "www.example.com"],
  "serial_number": "1234567890",
  "signature_algorithm": "sha256WithRSAEncryption",
  "subject": "CN=example.com, O=Example Org, C=US",
  "created_at": "2024-07-19T04:48:27.000-05:00"
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/custom\_certificates/:certificate\_uuid

## [Delete](https://docs.saascustomdomains.com/custom-certificates#delete)

This endpoint allows you to delete custom certificates.

### Request

DELETE

/api/v1/accounts/:account\_uuid/custom\_certificates/:certificate\_uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/custom_certificates/:certificate_uuid \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Custom certificate cert_1 deleted."
}

```

---

<!-- Source: https://docs.saascustomdomains.com/redirects -->

# Redirects

On this page, we'll dive into the different redirects endpoints you can use to manage redirects programmatically. We'll look at how to query, create, update, and delete redirects.

## [The redirect model](https://docs.saascustomdomains.com/redirects#the-redirect-model)

The redirect model contains all the information about your redirects.

### Properties

* `uuid` (string):  
Unique identifier for the redirect.
* `source_host` (string):  
The host where the incoming request arrives. For example, if you want to redirect requests from old-domain.com, this would be "old-domain.com".
* `match_path` (string):  
The path pattern to match for redirecting. Default value is "\*" which matches all paths. Can contain letters, numbers, dashes, underscores, forward slashes, and an optional trailing asterisk for prefix matching.
* `destination_url` (string):  
The URL where matching requests will be redirected to. This must be a valid URL where you want to send the traffic.
* `redirect_type` (string):  
The HTTP status code to use for the redirect. Can be one of:

  * `permanent` (HTTP code 301) - Default value, tells browsers and search engines that the redirect is permanent
  * `found` (HTTP code 302) - Indicates a temporary redirect
* `preserve_path` (boolean):  
Whether to preserve the original request path when redirecting. Default is true. When enabled, the original path will be appended to the destination URL.
* `preserve_query_string` (boolean):  
Whether to preserve the query string when redirecting. Default is true. When enabled, the original query parameters will be appended to the destination URL.
* `active` (boolean):  
Whether the redirect is currently active. Default is true. Inactive redirects won't redirect any traffic.
* `created_at` (timestamp):  
The date and time when the redirect was created.
* `updated_at` (timestamp):  
The date and time when the redirect was last updated.

---

GET

/api/v1/accounts/:account\_uuid/redirects

## [List redirects](https://docs.saascustomdomains.com/redirects#list-redirects)

This endpoint allows you to retrieve a paginated list of all redirects belonging to an account. By default, 20 redirects are shown per page.

### Optional attributes

* `source_host` (string):  
Filter redirects by source host.
* `page` (integer):  
The page number to retrieve. Default value is `1`.
* `per_page` (integer):  
The number of items to retrieve per page. Default value is `20`.
* `sort` (string):  
The field to sort by. Available fields: `created_at`, `updated_at`, `source_host`.

### Request

GET

/api/v1/accounts/:account\_uuid/redirects

```bash
curl -G https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/redirects \
  -H "Authorization: Bearer {token}" \
  -d source_host="old-domain.com" \
  -d page=1

```

### Response

```json
{
  "data": [
    {
      "uuid": "redirect_52069e63",
      "source_host": "old-domain.com",
      "match_path": "*",
      "destination_url": "https://new-domain.com",
      "redirect_type": "permanent",
      "preserve_path": true,
      "preserve_query_string": true,
      "active": true,
      "created_at": "2024-01-14T13:19:42.193+01:00",
      "updated_at": "2024-01-14T13:19:42.193+01:00",
      "account_uuid": "account_3f43d84c"
    },
    {
      "uuid": "redirect_d3ba54e7",
      // ...
    }
  ],
  "pagination": {
    "page": 1,
    "count": 42
  }
}

```

---

POST

/api/v1/accounts/:account\_uuid/redirects

## [Create](https://docs.saascustomdomains.com/redirects#create)

This endpoint allows you to create a new redirect.

### Required attributes

* `source_host` (string):  
The host where the incoming request arrives. This must be a valid hostname.
* `destination_url` (string):  
The URL where matching requests will be redirected to.

### Optional attributes

* `match_path` (string):  
The path pattern to match. Default is "\*" which matches all paths. Can contain letters, numbers, dashes, underscores, forward slashes, and an optional trailing asterisk for prefix matching.
* `redirect_type` (string):  
The HTTP status code to use for the redirect. Can be one of:

  * `permanent` (HTTP code 301) - Default value, tells browsers and search engines that the redirect is permanent
  * `found` (HTTP code 302) - Indicates a temporary redirect
* `preserve_path` (boolean):  
Whether to preserve the original path. Default is true.
* `preserve_query_string` (boolean):  
Whether to preserve query parameters. Default is true.
* `active` (boolean):  
Whether the redirect should be active. Default is true.

### Request

POST

/api/v1/accounts/:account\_uuid/redirects

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/redirects \
  -H "Authorization: Bearer {token}" \
  -d source_host="old-domain.com" \
  -d destination_url="https://new-domain.com"

```

### Response

```json
{
  "uuid": "redirect_52069e63",
  "source_host": "old-domain.com",
  "match_path": "*",
  "destination_url": "https://new-domain.com",
  "redirect_type": "permanent",
  "preserve_path": true,
  "preserve_query_string": true,
  "active": true,
  "created_at": "2024-01-14T13:19:42.193+01:00",
  "updated_at": "2024-01-14T13:19:42.193+01:00",
  "account_uuid": "account_3f43d84c"
}

```

---

PUT

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

## [Update](https://docs.saascustomdomains.com/redirects#update)

This endpoint allows you to update an existing redirect.

### Optional attributes

* `source_host` (string):  
The host where the incoming request arrives.
* `destination_url` (string):  
The URL where matching requests will be redirected to.
* `match_path` (string):  
The path pattern to match.
* `redirect_type` (string):  
The HTTP status code to use for the redirect. Can be one of:

  * `permanent` (HTTP code 301) - Default value, tells browsers and search engines that the redirect is permanent
  * `found` (HTTP code 302) - Indicates a temporary redirect
* `preserve_path` (boolean):  
Whether to preserve the original path.
* `preserve_query_string` (boolean):  
Whether to preserve query parameters.
* `active` (boolean):  
Whether the redirect should be active.

### Request

PUT

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

```bash
curl -X PUT https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/redirects/:redirect_uuid \
  -H "Authorization: Bearer {token}" \
  -d destination_url="https://updated-domain.com"

```

### Response

```json
{
  "uuid": "redirect_52069e63",
  "source_host": "old-domain.com",
  "match_path": "*",
  "destination_url": "https://updated-domain.com",
  "redirect_type": "permanent",
  "preserve_path": true,
  "preserve_query_string": true,
  "active": true,
  "created_at": "2024-01-14T13:19:42.193+01:00",
  "updated_at": "2024-01-14T13:25:16.421+01:00",
  "account_uuid": "account_3f43d84c"
}

```

---

GET

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

## [Retrieve](https://docs.saascustomdomains.com/redirects#retrieve)

This endpoint allows you to retrieve a redirect by providing its UUID. Refer to [the list](https://docs.saascustomdomains.com/redirects#the-redirect-model) at the top of this page to see which properties are included with redirect objects.

### Request

GET

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

```bash
curl https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/redirects/:redirect_uuid \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "uuid": "redirect_52069e63",
  "source_host": "old-domain.com",
  "match_path": "*",
  "destination_url": "https://new-domain.com",
  "redirect_type": "permanent",
  "preserve_path": true,
  "preserve_query_string": true,
  "active": true,
  "created_at": "2024-01-14T13:19:42.193+01:00",
  "updated_at": "2024-01-14T13:19:42.193+01:00",
  "account_uuid": "account_3f43d84c"
}

```

---

DELETE

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

## [Delete](https://docs.saascustomdomains.com/redirects#delete)

This endpoint allows you to delete a redirect.

### Request

DELETE

/api/v1/accounts/:account\_uuid/redirects/:redirect\_uuid

```bash
curl -X DELETE https://app.saascustomdomains.com/api/v1/accounts/:account_uuid/redirects/:redirect_uuid \
  -H "Authorization: Bearer {token}"

```

### Response

```json
{
  "message": "Redirect redirect_52069e63 (old-domain.com) deleted."
}

```

---

## [Error responses](https://docs.saascustomdomains.com/redirects#error-responses)

When the API encounters an error, it will respond with an appropriate HTTP status code and a JSON object containing error details.

### Example error responses

### 400 Bad Request - Invalid source\_host

```json
{
  "error": "Source host is not a valid hostname",
  "error_code": "source_host_invalid"
}

```

### 400 Bad Request - Invalid match\_path

```json
{
  "error": "Match path can only contain letters, numbers, dashes, underscores and an optional trailing asterisk",
  "error_code": "match_path_invalid"
}

```

### 400 Bad Request - Source host taken

```json
{
  "error": "Source host is already taken",
  "error_code": "source_host_taken"
}

```

### 404 Not Found

```json
{
  "error": "Redirect redirect_52069e63 not found.",
  "error_code": "not_found"
}

```

---

<!-- Source: https://docs.saascustomdomains.com/web-analytics -->

# Web Analytics

On this page, we'll explore the various endpoints that provide detailed web analytics data for your custom domains. From visitor counts and pageview statistics to traffic sources and user engagement metrics, these endpoints give you a complete picture of how users interact with your domains.

The web analytics API enables you to track and analyze key metrics including unique visitors, session data, page performance, user demographics, traffic sources, campaign effectiveness through UTM parameters, and device usage patterns. Whether you need high-level overview statistics or detailed behavioral insights, these endpoints provide the data you need to understand your audience and optimize your web presence.

---

## [Common Parameters](https://docs.saascustomdomains.com/web-analytics#common-parameters)

Analytics endpoints support the following common parameters:

* `from` (string):  
Start date for the analytics period in ISO 8601 format YYYY-MM-DD, e.g., "2024-01-01". If not provided, defaults to 30 days ago. Timezone is UTC.
* `to` (string):  
End date for the analytics period in ISO 8601 format YYYY-MM-DD, e.g., "2024-01-31". If not provided, defaults to current date. Timezone is UTC.
* `frequency` (string):  
(For time-series endpoints only) Time grouping frequency. Available values: `day`, `week`, `month`. Default: `day`. Ranges longer than 31 days return weekly data when you ask for `day`, and ranges longer than 181 days always return monthly data.

Time-series endpoints return one entry for every day, week or month in the range, including periods without traffic (value `0`). Each key is the first day of its period: the date itself for `day`, the Monday for `week`, and the 1st for `month`.

Analytics data is available from July 1, 2025, and earlier dates return zeros. Responses are cached for up to 5 minutes.

Analytics must be enabled for the account that owns the domain, otherwise these endpoints return `403 Forbidden`. A domain that doesn't belong to one of your accounts returns `404 Not Found`.

---

## [How web analytics are counted](https://docs.saascustomdomains.com/web-analytics#how-web-analytics-are-counted)

* Web analytics only count pageviews: successful (`2xx` or `304`) `GET` requests to paths without a file extension. Requests from known bots and datacenter IP addresses are excluded.
* A visitor is a unique IP address. A visit is one visitor within one clock hour (UTC).
* Visitor and visit counts are approximate, usually within 1–2% of the exact number.
* List endpoints return up to 200 entries sorted by count. `percentage` is each entry's share of the returned entries' total.

---

# Web Analytics

GET

/api/v1/analytics/custom\_domains/:uuid/web\_summary

## [Web summary](https://docs.saascustomdomains.com/web-analytics#web-summary)

This endpoint returns the main web analytics metrics in one request: unique visitors, pageviews, visits, and the top 10 pages and traffic sources. It's the same data as the [unique visitors count](https://docs.saascustomdomains.com/web-analytics#unique-visitors-count), [total pageviews count](https://docs.saascustomdomains.com/web-analytics#total-pageviews-count), [total visits count](https://docs.saascustomdomains.com/web-analytics#total-visits-count), [pages](https://docs.saascustomdomains.com/web-analytics#pages) and [traffic sources](https://docs.saascustomdomains.com/web-analytics#traffic-sources) endpoints.

In `top_pages` and `top_sources`, `percentage` is each entry's share of the top 200 entries' total, the same as in the full lists.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/web\_summary

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/web_summary \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "unique_visitors_count": 5250,
  "total_pageviews_count": 9426,
  "total_visits_count": 6468,
  "top_pages": [
    {
      "name": "/",
      "count": 1431,
      "percentage": 15.18
    },
    {
      "name": "/pricing",
      "count": 1162,
      "percentage": 12.33
    }
  ],
  "top_sources": [
    {
      "name": "google.com",
      "count": 2210,
      "percentage": 42.1
    },
    {
      "name": "Direct",
      "count": 1580,
      "percentage": 30.1
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/unique\_visitors\_count

## [Unique visitors count](https://docs.saascustomdomains.com/web-analytics#unique-visitors-count)

This endpoint returns the total number of unique visitors for a custom domain within the specified time range.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/unique\_visitors\_count

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/unique_visitors_count \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "count": 15234
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/unique\_visitors\_by\_date

## [Unique visitors by date](https://docs.saascustomdomains.com/web-analytics#unique-visitors-by-date)

This endpoint returns the number of unique visitors broken down by date.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/unique\_visitors\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/unique_visitors_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 523,
    "2024-01-02": 612,
    "2024-01-03": 598
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/total\_pageviews\_count

## [Total pageviews count](https://docs.saascustomdomains.com/web-analytics#total-pageviews-count)

This endpoint returns the total number of pageviews for a custom domain.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/total\_pageviews\_count

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/total_pageviews_count \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "count": 45678
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/pageviews\_by\_date

## [Pageviews by date](https://docs.saascustomdomains.com/web-analytics#pageviews-by-date)

This endpoint returns the number of pageviews broken down by date.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/pageviews\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/pageviews_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 1523,
    "2024-01-02": 1612,
    "2024-01-03": 1598,
    "2024-01-04": 1687
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/total\_visits\_count

## [Total visits count](https://docs.saascustomdomains.com/web-analytics#total-visits-count)

This endpoint returns the total number of visits (sessions) for a custom domain. A visit is one visitor within one clock hour (UTC).

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/total\_visits\_count

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/total_visits_count \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "count": 25789
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/visits\_by\_date

## [Visits by date](https://docs.saascustomdomains.com/web-analytics#visits-by-date)

This endpoint returns the number of visits (sessions) broken down by date. A visit is one visitor within one clock hour (UTC).

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/visits\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/visits_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 823,
    "2024-01-02": 912,
    "2024-01-03": 878,
    "2024-01-04": 945
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/pageviews\_per\_visit\_by\_date

## [Pageviews per visit by date](https://docs.saascustomdomains.com/web-analytics#pageviews-per-visit-by-date)

This endpoint returns the average number of pageviews per visit (session) broken down by date. This metric helps understand user engagement by showing how many pages users view in a single session.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/pageviews\_per\_visit\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/pageviews_per_visit_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 2.4,
    "2024-01-02": 2.7,
    "2024-01-03": 2.5,
    "2024-01-04": 2.8
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/sources

## [Traffic sources](https://docs.saascustomdomains.com/web-analytics#traffic-sources)

This endpoint returns the traffic sources for your custom domain, including referrers and their percentage distribution. `count` is the number of unique visitors. Visitors without a referrer are listed as `Direct`.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/sources

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/sources \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
        "name": "Direct",
        "count": 21344,
        "percentage": 83.94
    },
    {
        "name": "www.google.com",
        "count": 1835,
        "percentage": 7.22
    },
    // ... more sources
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/utm\_params

## [UTM parameters](https://docs.saascustomdomains.com/web-analytics#utm-parameters)

This endpoint returns the values of a UTM parameter in your traffic. `count` is the number of unique visitors.

### Required attributes

* `utm_param` (string):  
The UTM parameter to analyze (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`).

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/utm\_params

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/utm_params \
  -H "Authorization: Bearer {token}" \
  -d utm_param="source" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "newsletter",
      "count": 1234,
      "percentage": 58.48
    },
    {
      "name": "twitter",
      "count": 876,
      "percentage": 41.52
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/pages

## [Pages](https://docs.saascustomdomains.com/web-analytics#pages)

This endpoint returns the most viewed pages on your custom domain. `count` is the number of pageviews.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/pages

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/pages \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "/",
      "count": 12345,
      "percentage": 44.66
    },
    {
      "name": "/blog",
      "count": 8765,
      "percentage": 31.71
    },
    {
      "name": "/pricing",
      "count": 6543,
      "percentage": 23.63
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/locations

## [Locations](https://docs.saascustomdomains.com/web-analytics#locations)

This endpoint returns geographic analytics data about your visitors, broken down by country, region, or city. `count` is the number of unique visitors. `country_code` is the ISO country code, also for regions and cities.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `location_type` (string):  
Type of location data to return (`country`, `region`, `city`). Default: `country`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/locations

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/locations \
  -H "Authorization: Bearer {token}" \
  -d location_type="country" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "United States",
      "country_code": "US",
      "count": 45678,
      "percentage": 56.29
    },
    {
      "name": "United Kingdom",
      "country_code": "GB",
      "count": 23456,
      "percentage": 28.9
    },
    {
      "name": "Germany",
      "country_code": "DE",
      "count": 12345,
      "percentage": 15.21
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/devices

## [Devices](https://docs.saascustomdomains.com/web-analytics#devices)

This endpoint returns detailed analytics about the devices used to access your custom domain, broken down into three categories: browsers, operating systems, and screen sizes. `count` is the number of unique visitors.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/devices

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/devices \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "browsers": [
      {
        "name": "Chrome",
        "browser_id": "chrome",
        "count": 14510,
        "percentage": 50.39
      },
      {
        "name": "Firefox",
        "browser_id": "firefox",
        "count": 9703,
        "percentage": 33.7
      }
    ],
    "operating_systems": [
      {
        "name": "macOS",
        "operating_system_id": "mac",
        "count": 15528,
        "percentage": 53.93
      },
      {
        "name": "Windows",
        "operating_system_id": "windows",
        "count": 9481,
        "percentage": 32.93
      }
    ],
    "screen_sizes": [
      {
        "name": "Desktop",
        "screen_size_id": "desktop",
        "count": 26708,
        "percentage": 92.76
      },
      {
        "name": "Mobile",
        "screen_size_id": "mobile",
        "count": 1907,
        "percentage": 6.62
      }
    ]
  }
}

```

---

---

<!-- Source: https://docs.saascustomdomains.com/proxy-analytics -->

# Proxy Analytics

On this page, we'll explore the endpoints that provide detailed proxy-level analytics for your custom domains. These endpoints offer insights into the technical performance and operational aspects of your domain infrastructure, helping you monitor and optimize your service delivery.

The proxy analytics API provides comprehensive data about your domains' infrastructure performance, including request volumes, data transfer statistics, response times, and server behavior. Track metrics such as request counts, bandwidth usage, response time distributions, datacenter and bot traffic patterns, and HTTP response codes. These insights help you understand your domains' technical performance, identify potential issues, and ensure optimal service delivery to your users.

---

## [Common Parameters](https://docs.saascustomdomains.com/proxy-analytics#common-parameters)

Analytics endpoints support the following common parameters:

* `from` (string):  
Start date for the analytics period in ISO 8601 format YYYY-MM-DD, e.g., "2024-01-01". If not provided, defaults to 30 days ago. Timezone is UTC.
* `to` (string):  
End date for the analytics period in ISO 8601 format YYYY-MM-DD, e.g., "2024-01-31". If not provided, defaults to current date. Timezone is UTC.
* `frequency` (string):  
(For time-series endpoints only) Time grouping frequency. Available values: `day`, `week`, `month`. Default: `day`. Ranges longer than 31 days return weekly data when you ask for `day`, and ranges longer than 181 days always return monthly data.

Time-series endpoints return one entry for every day, week or month in the range, including periods without traffic (value `0`). Each key is the first day of its period: the date itself for `day`, the Monday for `week`, and the 1st for `month`.

Analytics data is available from July 1, 2025, and earlier dates return zeros. Responses are cached for up to 5 minutes.

Analytics must be enabled for the account that owns the domain, otherwise these endpoints return `403 Forbidden`. A domain that doesn't belong to one of your accounts returns `404 Not Found`.

---

## [How proxy analytics are counted](https://docs.saascustomdomains.com/proxy-analytics#how-proxy-analytics-are-counted)

Proxy analytics count every request to your domain, including bots and datacenter traffic. List endpoints return up to 200 entries sorted by count. `percentage` is each entry's share of the returned entries' total.

---

GET

/api/v1/analytics/custom\_domains/:uuid/proxy\_summary

## [Proxy summary](https://docs.saascustomdomains.com/proxy-analytics#proxy-summary)

This endpoint returns the main proxy metrics in one request: the request count, total data transfer, average response time and response codes. It's the same data as the [request count](https://docs.saascustomdomains.com/proxy-analytics#request-count), [total data transfer](https://docs.saascustomdomains.com/proxy-analytics#total-data-transfer), [average response time](https://docs.saascustomdomains.com/proxy-analytics#average-response-time) and [response codes](https://docs.saascustomdomains.com/proxy-analytics#response-codes) endpoints.

`average_response_time_seconds` is how long your upstream took to respond, averaged over the requests that were forwarded to it. `response_codes` lists up to 200 entries, sorted by count.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/proxy\_summary

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/proxy_summary \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "request_count": 455630,
  "total_data_transfer_bytes": 28356341760,
  "average_response_time_seconds": 0.245,
  "response_codes": [
    {
      "name": "200 (OK)",
      "http_code": 200,
      "count": 235621,
      "percentage": 51.69
    },
    {
      "name": "301 (Moved Permanently)",
      "http_code": 301,
      "count": 143997,
      "percentage": 31.59
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/request\_count

## [Request count](https://docs.saascustomdomains.com/proxy-analytics#request-count)

This endpoint returns the total number of HTTP requests processed for your custom domain.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/request\_count

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/request_count \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "count": 123456
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/request\_counts\_by\_date

## [Request counts by date](https://docs.saascustomdomains.com/proxy-analytics#request-counts-by-date)

This endpoint returns the number of HTTP requests broken down by date.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/request\_counts\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/request_counts_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 45678,
    "2024-01-02": 52341,
    "2024-01-03": 48765,
    "2024-01-04": 51234
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/total\_data\_transfer

## [Total data transfer](https://docs.saascustomdomains.com/proxy-analytics#total-data-transfer)

This endpoint returns the total amount of data transferred for your custom domain. This includes both incoming and outgoing traffic. The value is in bytes.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/total\_data\_transfer

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/total_data_transfer \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "bytes": 1234567890
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/data\_transfer\_by\_date

## [Data transfer by date](https://docs.saascustomdomains.com/proxy-analytics#data-transfer-by-date)

This endpoint returns the amount of data transferred in bytes broken down by date. This includes both incoming and outgoing traffic.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/data\_transfer\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/data_transfer_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-09-01" \
  -d to="2024-09-29"

```

### Response

```json
{
  "data": {
    "2024-01-01": 653420657,
    "2024-01-02": 1318207597,
    "2024-01-03": 710554739,
    "2024-01-04": 0
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/average\_response\_time

## [Average response time](https://docs.saascustomdomains.com/proxy-analytics#average-response-time)

This endpoint returns how long your upstream took to respond, averaged over the requests that were forwarded to it. The duration is in seconds.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/average\_response\_time

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/average_response_time \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "duration": 0.245
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/average\_response\_time\_by\_date

## [Average response time by date](https://docs.saascustomdomains.com/proxy-analytics#average-response-time-by-date)

This endpoint returns how long your upstream took to respond, averaged over the requests that were forwarded to it, broken down by date. The duration is in seconds.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.
* `frequency` (string):  
Time grouping frequency (`day`, `week`, `month`). Default: `day`.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/average\_response\_time\_by\_date

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/average_response_time_by_date \
  -H "Authorization: Bearer {token}" \
  -d frequency="day" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "2024-01-01": 0.234,
    "2024-01-02": 0.255,
    "2024-01-03": 0.223,
    "2024-01-04": 0.215
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/response\_time\_distribution

## [Response time distribution](https://docs.saascustomdomains.com/proxy-analytics#response-time-distribution)

This endpoint returns the number of requests in each upstream response time bucket. Only requests that were forwarded to your upstream are counted, and buckets without requests are left out.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/response\_time\_distribution

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/response_time_distribution \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": {
    "0-100ms": 10676,
    "100-200ms": 5673,
    "200-300ms": 515,
    "300-400ms": 4272,
    "400-500ms": 9762,
    "500-600ms": 11376,
    "600-700ms": 7644,
    "700-800ms": 4976,
    "800-900ms": 4579,
    "900ms-1s": 2465,
    "1-1.5s": 3271,
    "1.5-2s": 1368,
    "2-3s": 514,
    "3-5s": 512,
    "5-10s": 217,
    "10s+": 140
  }
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/response\_codes

## [Response codes](https://docs.saascustomdomains.com/proxy-analytics#response-codes)

This endpoint returns a breakdown of HTTP response codes returned by your custom domain, including counts and percentages for each status code.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/response\_codes

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/response_codes \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "200 (OK)",
      "http_code": 200,
      "count": 235621,
      "percentage": 51.69
    },
    {
      "name": "301 (Moved Permanently)",
      "http_code": 301,
      "count": 143997,
      "percentage": 31.59
    },
    {
      "name": "302 (Found)",
      "http_code": 302,
      "count": 32012,
      "percentage": 7.02
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/bot\_requests

## [Bot requests](https://docs.saascustomdomains.com/proxy-analytics#bot-requests)

This endpoint returns analytics about requests from known bots and crawlers, including search engines, monitoring services, and other automated agents.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.
* `to` (string):  
End date for the analytics period (ISO 8601 format). Defaults to current date.

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/bot\_requests

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/bot_requests \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "CloudFlare-AlwaysOnline",
      "count": 221892,
      "percentage": 55.43
    },
    {
      "name": "Google Bot",
      "count": 43506,
      "percentage": 10.87
    },
    {
      "name": "Empty User-Agent",
      "count": 42540,
      "percentage": 10.63
    }
  ]
}

```

---

GET

/api/v1/analytics/custom\_domains/:uuid/datacenter\_requests

## [Datacenter requests](https://docs.saascustomdomains.com/proxy-analytics#datacenter-requests)

This endpoint returns analytics about requests originating from different datacenters.

### Optional attributes

* `from` (string):  
Start date for the analytics period (ISO 8601 format).
* `to` (string):  
End date for the analytics period (ISO 8601 format).

### Request

GET

/api/v1/analytics/custom\_domains/:uuid/datacenter\_requests

```bash
curl -G https://app.saascustomdomains.com/api/v1/analytics/custom_domains/:uuid/datacenter_requests \
  -H "Authorization: Bearer {token}" \
  -d from="2024-01-01" \
  -d to="2024-01-31"

```

### Response

```json
{
  "data": [
    {
      "name": "Cloudflare",
      "datacenter_id": "cloudflare",
      "count": 222999,
      "percentage": 70.94
    },
    {
      "name": "GitHub",
      "datacenter_id": "github",
      "count": 57785,
      "percentage": 18.38
    }
  ]
}

```

---
